Browse Source

Merge branch 'skiasharp'

pull/509/head
Steven Kirk 10 years ago
parent
commit
ef6aae7f4f
  1. 436
      Perspex.sln
  2. 2
      nuget/build-version.ps1
  3. 4
      samples/ControlCatalog/ControlCatalog.csproj
  4. 11
      samples/ControlCatalog/Program.cs
  5. 96
      samples/TestApplicationShared/App.cs
  6. 8
      src/Perspex.Controls/Image.cs
  7. 15
      src/Shared/PlatformSupport/AssetLoader.cs
  8. 2
      src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj.bak
  9. 50
      src/Skia/Perspex.Skia.Android/AndroidRenderTarget.cs
  10. 29
      src/Skia/Perspex.Skia.Android/MethodTable.cs
  11. 12
      src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj
  12. 4
      src/Skia/Perspex.Skia.Android/packages.config
  13. 235
      src/Skia/Perspex.Skia.Desktop/MethodTableImpl.cs
  14. 56
      src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj
  15. 4
      src/Skia/Perspex.Skia.Desktop/packages.config
  16. 2
      src/Skia/Perspex.Skia.iOS.TestApp/Info.plist
  17. 2
      src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj
  18. 15
      src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj
  19. 27
      src/Skia/Perspex.Skia.iOS/SkiaView.cs
  20. 19
      src/Skia/Perspex.Skia.iOS/iOSMethodTable.cs
  21. 4
      src/Skia/Perspex.Skia.iOS/packages.config
  22. 63
      src/Skia/Perspex.Skia/BitmapImpl.cs
  23. 279
      src/Skia/Perspex.Skia/DrawingContextImpl.cs
  24. 266
      src/Skia/Perspex.Skia/FormattedTextImpl.cs
  25. 238
      src/Skia/Perspex.Skia/MethodTable.cs
  26. 113
      src/Skia/Perspex.Skia/NativeBrush.cs
  27. 10
      src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs
  28. 23
      src/Skia/Perspex.Skia/NativeFormattedText.cs
  29. 12
      src/Skia/Perspex.Skia/Perspex.Skia.projitems
  30. 106
      src/Skia/Perspex.Skia/PerspexHandleHolder.cs
  31. 19
      src/Skia/Perspex.Skia/PlatformRenderInterface.cs
  32. 174
      src/Skia/Perspex.Skia/RenderTarget.cs
  33. 26
      src/Skia/Perspex.Skia/SkRect.cs
  34. 6
      src/Skia/Perspex.Skia/SkiaPlatform.cs
  35. 29
      src/Skia/Perspex.Skia/SkiaPoint.cs
  36. 69
      src/Skia/Perspex.Skia/SkiaSharpExtensions.cs
  37. 135
      src/Skia/Perspex.Skia/StreamGeometryImpl.cs
  38. 48
      src/Skia/Perspex.Skia/TypefaceCache.cs
  39. 21
      src/Skia/Perspex.Skia/WindowDrawingContextImpl.cs
  40. 46
      src/Skia/Perspex.Skia/readme.md
  41. 67
      src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
  42. 21
      src/Windows/Perspex.Win32/SystemDialogImpl.cs
  43. 3
      src/iOS/Perspex.iOS/Perspex.iOS.csproj
  44. 75
      src/iOS/Perspex.iOS/PerspexAppDelegate.cs
  45. 27
      src/iOS/Perspex.iOS/PerspexView.cs
  46. 33
      src/iOS/Perspex.iOS/WindowingPlatformImpl.cs
  47. 74
      src/iOS/Perspex.iOS/iOSPlatform.cs
  48. 20
      src/iOS/Perspex.iOSTestApplication/AppDelegate.cs
  49. 68
      src/iOS/Perspex.iOSTestApplication/Info.plist
  50. 8
      src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj

436
Perspex.sln

File diff suppressed because it is too large

2
nuget/build-version.ps1

@ -65,7 +65,7 @@ Copy-Item skia\build $skia_root -recurse
mkdir $skia_native
Copy-Item ..\src\Skia\native\Windows $skia_native -recurse
Copy-Item ..\src\Skia\native\Linux $skia_native -recurse
Copy-Item ..\src\Skia\Perspex.Skia.Desktop\bin\Release\Perspex.Skia.Desktop.dll $skia_lib
Copy-Item ..\src\Skia\Perspex.Skia.Desktop\bin\x86\Release\Perspex.Skia.Desktop.dll $skia_lib
Copy-Item ..\src\Android\Perspex.Android\bin\Release\Perspex.Android.dll $android

4
samples/ControlCatalog/ControlCatalog.csproj

@ -211,6 +211,10 @@
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Perspex.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Skia\Perspex.Skia.Desktop\Perspex.Skia.Desktop.csproj">
<Project>{925dd807-b651-475f-9f7c-cbeb974ce43d}</Project>
<Name>Perspex.Skia.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Perspex.Direct2D1\Perspex.Direct2D1.csproj">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Perspex.Direct2D1</Name>

11
samples/ControlCatalog/Program.cs

@ -110,12 +110,11 @@ namespace ControlCatalog
{
app.UseWin32();
// not available until we do the SkiaSharp merge
//if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia)
//{
// app.UseSkia();
//}
//else
if (args.Contains("--skia") || DefaultRenderSystem() == RenderSystem.Skia)
{
app.UseSkia();
}
else
{
app.UseDirect2D();
}

96
samples/TestApplicationShared/App.cs

@ -11,6 +11,7 @@ using Perspex.Themes.Default;
using Perspex.Diagnostics;
using Perspex.Platform;
using Perspex.Shared.PlatformSupport;
using Perspex.Media;
namespace TestApplication
{
@ -18,35 +19,74 @@ namespace TestApplication
{
public App()
{
// TODO: I believe this has to happen before we select sub systems. Can we
// move this safely into Application itself? Is there anything in here
// that is platform specific??
//
// TODO: I believe this has to happen before we select sub systems. Can we
// move this safely into Application itself? Is there anything in here
// that is platform specific??
//
RegisterServices();
}
public void Run()
{
Styles.Add(new DefaultTheme());
var loader = new PerspexXamlLoader();
var baseLight = (IStyle)loader.Load(
new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default"));
Styles.Add(baseLight);
Styles.Add(new SampleTabStyle());
DataTemplates = new DataTemplates
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock {Text = x.Name},
x => x.Children),
};
MainWindow.RootNamespace = "TestApplication";
var wnd = MainWindow.Create();
DevTools.Attach(wnd);
Run(wnd);
}
}
public void Run()
{
Styles.Add(new DefaultTheme());
var loader = new PerspexXamlLoader();
var baseLight = (IStyle)loader.Load(
new Uri("resm:Perspex.Themes.Default.Accents.BaseLight.xaml?assembly=Perspex.Themes.Default"));
Styles.Add(baseLight);
Styles.Add(new SampleTabStyle());
DataTemplates = new DataTemplates
{
new FuncTreeDataTemplate<Node>(
x => new TextBlock {Text = x.Name},
x => x.Children),
};
MainWindow.RootNamespace = "TestApplication";
var wnd = MainWindow.Create();
wnd.AttachDevTools();
Run(wnd);
}
// This provides a simple UI tree for testing input handling, drawing, etc
public static Window CreateSimpleWindow()
{
Window window = new Window
{
Title = "Perspex Test Application",
Background = Brushes.Red,
Content = new StackPanel
{
Margin = new Thickness(30),
Background = Brushes.Yellow,
Children = new Controls
{
new TextBlock
{
Text = "TEXT BLOCK",
Width = 300,
Height = 40,
Background = Brushes.White,
Foreground = Brushes.Black
},
new Button
{
Content = "BUTTON",
Width = 150,
Height = 40,
Background = Brushes.LightGreen,
Foreground = Brushes.Black
}
}
}
};
return window;
}
}
}

8
src/Perspex.Controls/Image.cs

@ -23,7 +23,13 @@ namespace Perspex.Controls
/// </summary>
public static readonly StyledProperty<Stretch> StretchProperty =
PerspexProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
static Image()
{
AffectsRender(SourceProperty);
AffectsRender(StretchProperty);
}
/// <summary>
/// Gets or sets the bitmap image that will be displayed.
/// </summary>

15
src/Shared/PlatformSupport/AssetLoader.cs

@ -105,7 +105,20 @@ namespace Perspex.Shared.PlatformSupport
"No defaultAssembly, entry assembly or explicit assembly specified, don't know where to look up for the resource, try specifiyng assembly explicitly");
IAssetDescriptor rv;
asm.Resources.TryGetValue(uri.AbsolutePath, out rv);
var resourceKey = uri.AbsolutePath;
#if __IOS__
// TODO: HACK: to get iOS up and running. Using Shared projects for resources
// is flawed as this alters the reource key locations across platforms
// I think we need to use Portable libraries from now on to avoid that.
if(asm.Name.Contains("iOS"))
{
resourceKey = resourceKey.Replace("TestApplication", "Perspex.iOSTestApplication");
}
#endif
asm.Resources.TryGetValue(resourceKey, out rv);
return rv;
}
throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri));

2
src/Skia/Perspex.Skia.Android.TestApp/Perspex.Skia.Android.TestApp.csproj.bak

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>True</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v6.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

50
src/Skia/Perspex.Skia.Android/AndroidRenderTarget.cs

@ -14,51 +14,11 @@ using Perspex.Platform;
namespace Perspex.Skia
{
class RenderTarget : IRenderTarget
/// <summary>
/// We will likely need platform specific pieces to support HW acceleration
/// so leaving this class here for now as placeholder.
/// </summary>
internal partial class RenderTarget : IRenderTarget
{
private IntPtr _currentRenderTarget = IntPtr.Zero;
private Surface _currentSurface = null;
private SurfaceView _view = null;
public RenderTarget(IPlatformHandle handle)
{
_view = (SurfaceView) handle;
}
public void Dispose()
{
if (_currentRenderTarget != IntPtr.Zero)
{
MethodTable.Instance.DisposeRenderTarget(_currentRenderTarget);
_currentRenderTarget = IntPtr.Zero;
GC.SuppressFinalize(this);
}
}
~RenderTarget()
{
Dispose();
}
public DrawingContext CreateDrawingContext()
{
var surface = _view.Holder.Surface;
if (surface == null)
throw new InvalidOperationException("Surface isn't available");
if (_currentSurface != surface)
{
_currentSurface = null;
if (_currentRenderTarget != IntPtr.Zero)
{
MethodTable.Instance.DisposeRenderTarget(_currentRenderTarget);
_currentRenderTarget = IntPtr.Zero;
}
_currentRenderTarget = MethodTable.Instance.CreateWindowRenderTarget(surface.Handle);
_currentSurface = surface;
}
return new DrawingContext(
new DrawingContextImpl(MethodTable.Instance.RenderTargetCreateRenderingContext(_currentRenderTarget)));
}
}
}

29
src/Skia/Perspex.Skia.Android/MethodTable.cs

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace Perspex.Skia
{
class MethodTableImpl : MethodTable
{
[DllImport(@"perspesk")]
private static extern IntPtr GetPerspexMethodTable();
[DllImport(@"perspesk")]
private static extern IntPtr PerspexJniInit(IntPtr jniEnv);
public MethodTableImpl() : base(GetPerspexMethodTable())
{
PerspexJniInit(JNIEnv.Handle);
}
}
}

12
src/Skia/Perspex.Skia.Android/Perspex.Skia.Android.csproj

@ -22,7 +22,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG;ANDROID</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -31,7 +31,7 @@
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<DefineConstants>TRACE;ANDROID</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -39,6 +39,10 @@
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\MonoAndroid\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -83,7 +87,6 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AndroidRenderTarget.cs" />
<Compile Include="MethodTable.cs" />
<Compile Include="SkiaRenderView.cs" />
<Compile Include="SkiaView.cs" />
</ItemGroup>
@ -95,6 +98,9 @@
<Link>native\x86\libperspesk.so</Link>
</EmbeddedNativeLibrary>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />

4
src/Skia/Perspex.Skia.Android/packages.config

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="monoandroid60" />
</packages>

235
src/Skia/Perspex.Skia.Desktop/MethodTableImpl.cs

@ -1,235 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
//Library loaders were taken from https://github.com/kekekeks/evhttp-sharp/tree/master/EvHttpSharp/Interop (MIT licensed)
namespace Perspex.Skia
{
class MethodTableImpl : MethodTable
{
public MethodTableImpl() : base(GetMethodTable())
{
}
class DetectedPlatformInfo
{
public DetectedPlatformInfo(IDynLoader loader, string name, string cpuArch, string dllExtension)
{
Loader = loader;
Name = name;
CpuArch = cpuArch;
DllExtension = dllExtension;
}
public IDynLoader Loader { get; }
public string Name { get; }
public string CpuArch { get;}
public string DllExtension { get; }
}
static DetectedPlatformInfo DetectPlatform()
{
var macInfo = new DetectedPlatformInfo(new OsxLoader(), "Darwin", IntPtr.Size == 4 ? "i686" : "x86_64", ".dylib");
switch (Environment.OSVersion.Platform)
{
case PlatformID.Unix:
// Well, there are chances MacOSX is reported as Unix instead of MacOSX.
// Instead of platform check, we'll do a feature checks (Mac specific root folders)
if (Directory.Exists("/Applications")
& Directory.Exists("/System")
& Directory.Exists("/Users")
& Directory.Exists("/Volumes"))
return macInfo;
else
{
string cpuArch;
using (var proc =
Process.Start(
(new ProcessStartInfo("uname", "-p")
{
UseShellExecute = false,
RedirectStandardOutput = true
})))
{
proc.WaitForExit();
cpuArch = proc.StandardOutput.ReadToEnd().Trim();
}
return new DetectedPlatformInfo(new LinuxLoader(), "Linux", cpuArch, ".so");
}
case PlatformID.MacOSX:
return macInfo;
default:
return new DetectedPlatformInfo(new Win32Loader(), "Windows", IntPtr.Size == 4 ? "i686" : "x86_64", ".dll");
}
}
static IEnumerable<string> GeneratePossiblePaths()
{
foreach (
var basePath in
new[] {Assembly.GetEntryAssembly(), typeof (MethodTable).Assembly}.Select(
a => Path.GetDirectoryName(a?.GetModules()?[0]?.FullyQualifiedName))
.Concat(new[] { Directory.GetCurrentDirectory()}))
{
if(basePath == null)
continue;
yield return Path.Combine(basePath);
yield return Path.Combine(basePath, "native");
yield return Path.Combine(basePath, "lib");
yield return Path.Combine(basePath, "lib", "native");
}
}
static IntPtr GetMethodTable()
{
var plat = DetectPlatform();
var name = "libperspesk" + plat.DllExtension;
var paths = GeneratePossiblePaths().Select(p => Path.Combine(p, plat.Name, plat.CpuArch, name)).ToList();
var dll = paths.FirstOrDefault(File.Exists);
var msg = "Unable to find native library in following paths: '" +
string.Join("', '", paths) + "'";
Console.Error.WriteLine(msg);
if (dll == null)
throw new FileNotFoundException(msg);
IntPtr hLib = plat.Loader.LoadLibrary(dll);
IntPtr pGetMethodTable = plat.Loader.GetProcAddress(hLib, "GetPerspexMethodTable");
var getTable = (GetPerspexMethodTableDelegate)
Marshal.GetDelegateForFunctionPointer(pGetMethodTable, typeof (GetPerspexMethodTableDelegate));
return getTable();
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate IntPtr GetPerspexMethodTableDelegate();
internal interface IDynLoader
{
IntPtr LoadLibrary(string dll);
IntPtr GetProcAddress(IntPtr dll, string proc);
}
class LinuxLoader : IDynLoader
{
// ReSharper disable InconsistentNaming
[DllImport("libdl.so.2")]
private static extern IntPtr dlopen(string path, int flags);
[DllImport("libdl.so.2")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("libdl.so.2")]
private static extern IntPtr dlerror();
// ReSharper restore InconsistentNaming
static string DlError()
{
return Marshal.PtrToStringAuto(dlerror());
}
public IntPtr LoadLibrary(string dll)
{
var handle = dlopen(dll, 1);
if (handle == IntPtr.Zero)
throw new Win32Exception(DlError());
return handle;
}
public IntPtr GetProcAddress(IntPtr dll, string proc)
{
var ptr = dlsym(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(DlError());
return ptr;
}
}
internal class Win32Loader : IDynLoader
{
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpszLib);
[DllImport("kernel32", EntryPoint = "SetDllDirectoryW", SetLastError = true, CharSet = CharSet.Unicode)]
extern static bool SetDllDirectory(string lpPathName);
[DllImport("kernel32", EntryPoint = "GetDllDirectoryW", SetLastError = true, CharSet = CharSet.Unicode)]
extern static int GetDllDirectory(int nBufferLength, char[] lpBuffer);
IntPtr IDynLoader.LoadLibrary(string dll)
{
var buffer = new char[2048];
int oldLen = GetDllDirectory(buffer.Length, buffer);
var oldDir = new string(buffer, 0, oldLen);
SetDllDirectory(Path.GetDirectoryName(dll));
var handle = LoadLibrary(dll);
if (handle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
SetDllDirectory(oldDir);
return handle;
}
IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc)
{
var ptr = GetProcAddress(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
return ptr;
}
}
class OsxLoader : IDynLoader
{
// ReSharper disable InconsistentNaming
[DllImport("/usr/lib/libSystem.dylib")]
public static extern IntPtr dlopen(string path, int mode);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlsym(IntPtr handle, string symbol);
[DllImport("/usr/lib/libSystem.dylib")]
private static extern IntPtr dlerror();
// ReSharper restore InconsistentNaming
static string DlError()
{
return Marshal.PtrToStringAuto(dlerror());
}
public IntPtr LoadLibrary(string dll)
{
var handle = dlopen(dll, 1);
if (handle == IntPtr.Zero)
throw new Win32Exception(DlError());
return handle;
}
public IntPtr GetProcAddress(IntPtr dll, string proc)
{
var ptr = dlsym(dll, proc);
if (ptr == IntPtr.Zero)
throw new Win32Exception(DlError());
return ptr;
}
}
}
}

56
src/Skia/Perspex.Skia.Desktop/Perspex.Skia.Desktop.csproj

@ -12,6 +12,8 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -22,6 +24,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -32,7 +35,31 @@
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;WIN32</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;WIN32</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\net45\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -43,8 +70,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MethodTableImpl.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="..\..\Windows\Perspex.Win32\Interop\UnmanagedMethods.cs">
<Link>UnmanagedMethods.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">
@ -81,27 +110,18 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="..\native\Windows\i686\libEGL.dll">
<Link>native\Windows\i686\libEGL.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\native\Windows\i686\libGLESv2.dll">
<Link>native\Windows\i686\libGLESv2.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\native\Windows\i686\libperspesk.dll">
<Link>native\Windows\i686\libperspesk.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="..\native\Linux\x86_64\libperspesk.so">
<Link>native\Linux\x86_64\libperspesk.so</Link>
</None>
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets" Condition="Exists('..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SkiaSharp.1.49.2.0-beta\build\net45\SkiaSharp.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

4
src/Skia/Perspex.Skia.Desktop/packages.config

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="net45" />
</packages>

2
src/Skia/Perspex.Skia.iOS.TestApp/Info.plist

@ -13,7 +13,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>6.0</string>
<string>8.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>

2
src/Skia/Perspex.Skia.iOS.TestApp/Perspex.Skia.iOS.TestApp.csproj

@ -20,7 +20,7 @@
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>i386</MtouchArch>
<MtouchLink>None</MtouchLink>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchDebug>true</MtouchDebug>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">

15
src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj

@ -2,7 +2,7 @@
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{47BE08A7-5985-410B-9FFC-2264B8EA595F}</ProjectGuid>
@ -36,16 +36,14 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="iOSMethodTable.cs" />
<Compile Include="SkiaView.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\native\iOS\libperspesk_standalone.a">
<Link>libperspesk_standalone.a</Link>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Reference Include="SkiaSharp, Version=1.49.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\SkiaSharp.1.49.2.0-beta\lib\XamariniOS\SkiaSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
@ -89,6 +87,9 @@
<Name>Perspex.Themes.Default</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\Perspex.Skia\Perspex.Skia.projitems" Label="Shared" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />

27
src/Skia/Perspex.Skia.iOS/SkiaView.cs

@ -15,32 +15,25 @@ using UIKit;
namespace Perspex.Skia.iOS
{
public abstract class SkiaView : GLKView
// TODO: This implementation will be revised as part of HW acceleration work
// and we may use the GLKView as a base for the implementation.
//
public abstract class SkiaView : UIView
{
[DllImport("__Internal")]
static extern IntPtr GetPerspexEAGLContext();
bool _drawQueued;
CADisplayLink _link;
static EAGLContext GetContext()
{
//Ensure initialization
MethodTable.Instance.SetOption((MethodTable.Option)0x10009999, IntPtr.Zero);
var ctx = GetPerspexEAGLContext();
var rv = Runtime.GetNSObject<EAGLContext>(ctx);
rv.DangerousRetain();
return rv;
return null;
}
protected SkiaView(Action<Action> registerFrame) : base(UIScreen.MainScreen.ApplicationFrame, GetContext())
protected SkiaView(Action<Action> registerFrame) : base(UIScreen.MainScreen.ApplicationFrame)
{
registerFrame(OnFrame);
}
protected SkiaView() : base(UIScreen.MainScreen.ApplicationFrame, GetContext())
protected SkiaView() : base(UIScreen.MainScreen.ApplicationFrame)
{
(_link = CADisplayLink.Create(() => OnFrame())).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode);
}
protected void OnFrame()
@ -48,7 +41,7 @@ namespace Perspex.Skia.iOS
if (_drawQueued)
{
_drawQueued = false;
Display();
this.SetNeedsDisplay();
}
}
@ -57,9 +50,9 @@ namespace Perspex.Skia.iOS
_drawQueued = true;
}
protected IPlatformHandle PerspexPlatformHandle { get; }
protected IPlatformHandle PerspexPlatformHandle { get; }
= new PlatformHandle(IntPtr.Zero, "Null (iOS-specific)");
protected abstract void Draw();

19
src/Skia/Perspex.Skia.iOS/iOSMethodTable.cs

@ -1,19 +0,0 @@
using System;
using System.Runtime.InteropServices;
using ObjCRuntime;
[assembly: LinkWith("Perspex.Skia.iOS.libperspesk_standalone.a", ForceLoad = true, SmartLink = true, IsCxx = true, Frameworks = "ImageIO MobileCoreServices CoreText")]
namespace Perspex.Skia
{
class MethodTableImpl : MethodTable
{
[DllImport("__Internal")]
static extern IntPtr GetPerspexMethodTable();
public MethodTableImpl() : base(GetPerspexMethodTable())
{
}
}
}

4
src/Skia/Perspex.Skia.iOS/packages.config

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="SkiaSharp" version="1.49.2.0-beta" targetFramework="xamarinios10" />
</packages>

63
src/Skia/Perspex.Skia/BitmapImpl.cs

@ -5,50 +5,57 @@ using System.Runtime.InteropServices;
using System.Text;
using Perspex.Media;
using Perspex.Platform;
using SkiaSharp;
namespace Perspex.Skia
{
class BitmapImpl : PerspexHandleHolder, IRenderTargetBitmapImpl
class BitmapImpl : IRenderTargetBitmapImpl
{
public void Save(string fileName)
public SKBitmap Bitmap { get; private set; }
public BitmapImpl(SKBitmap bm)
{
var ext = Path.GetExtension(fileName)?.ToLower();
var type = MethodTable.SkiaImageType.Png;
if(ext=="gif")
type = MethodTable.SkiaImageType.Gif;
if(ext=="jpeg" || ext =="jpg")
type = MethodTable.SkiaImageType.Jpeg;
var skdata = MethodTable.Instance.SaveImage(Handle, type, 100);
var size = MethodTable.Instance.GetSkDataSize(skdata);
var buffer = new byte[size];
MethodTable.Instance.ReadSkData(skdata, buffer, size);
File.WriteAllBytes(fileName, buffer);
Bitmap = bm;
PixelHeight = bm.Width;
PixelWidth = bm.Height;
}
public int PixelWidth { get; private set; }
public int PixelHeight { get; private set; }
protected override void Delete(IntPtr handle)
public BitmapImpl(int width, int height)
{
MethodTable.Instance.DisposeImage(handle);
throw new NotImplementedException();
}
public DrawingContext CreateDrawingContext()
public void Dispose()
{
return
new DrawingContext(
new DrawingContextImpl(MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
}
public BitmapImpl(IntPtr handle, int width, int height) : base(handle)
public void Save(string fileName)
{
PixelHeight = height;
PixelWidth = width;
// TODO: Implement this for SkiaSharp
throw new NotImplementedException();
//var ext = Path.GetExtension(fileName)?.ToLower();
//var type = MethodTable.SkiaImageType.Png;
//if(ext=="gif")
// type = MethodTable.SkiaImageType.Gif;
//if(ext=="jpeg" || ext =="jpg")
// type = MethodTable.SkiaImageType.Jpeg;
//var skdata = MethodTable.Instance.SaveImage(Handle, type, 100);
//var size = MethodTable.Instance.GetSkDataSize(skdata);
//var buffer = new byte[size];
//MethodTable.Instance.ReadSkData(skdata, buffer, size);
//File.WriteAllBytes(fileName, buffer);
}
public BitmapImpl(int width, int height)
: this(MethodTable.Instance.CreateRenderTargetBitmap(width, height), width, height)
public int PixelWidth { get; private set; }
public int PixelHeight { get; private set; }
public DrawingContext CreateDrawingContext()
{
return
new DrawingContext(
new DrawingContextImpl(null)); // MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
}
}
}

279
src/Skia/Perspex.Skia/DrawingContextImpl.cs

@ -2,182 +2,260 @@
using System.Collections.Generic;
using Perspex.Media;
using Perspex.Media.Imaging;
using Perspex.RenderHelpers;
using SkiaSharp;
using System.Linq;
namespace Perspex.Skia
{
unsafe class DrawingContextImpl : PerspexHandleHolder, IDrawingContextImpl
internal class DrawingContextImpl : IDrawingContextImpl
{
private readonly NativeDrawingContextSettings* _settings;
public DrawingContextImpl(IntPtr handle) : base(handle)
public SKCanvas Canvas { get; private set; }
public DrawingContextImpl(SKCanvas canvas)
{
_settings = MethodTable.Instance.GetDrawingContextSettingsPtr(handle);
_settings->Opacity = 1;
Canvas = canvas;
}
protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderingContext(handle);
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect)
{
var impl = (BitmapImpl) source.PlatformImpl;
var s = SkRect.FromRect(sourceRect);
var d = SkRect.FromRect(destRect);
MethodTable.Instance.DrawImage(Handle, impl.Handle, (float) opacity, ref s, ref d);
var impl = (BitmapImpl)source.PlatformImpl;
var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect();
Canvas.DrawBitmap(impl.Bitmap, s, d);
}
public void DrawLine(Pen pen, Point p1, Point p2)
{
using (var brush = CreateBrush(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
MethodTable.Instance.DrawLine(Handle, brush.Brush,
(float) p1.X, (float) p1.Y, (float) p2.X, (float) p2.Y);
using (var paint = CreatePaint(pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{
Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, paint);
}
}
static readonly NativeBrushContainer _dummy = new NativeBrushContainer(null);
public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry)
{
var impl = ((StreamGeometryImpl) geometry.PlatformImpl);
var impl = ((StreamGeometryImpl)geometry.PlatformImpl);
var size = geometry.Bounds.Size;
using(var fill = brush!=null?CreateBrush(brush, size):null)
using (var stroke = pen?.Brush != null ? CreateBrush(pen, size) : null)
using (var fill = brush != null ? CreatePaint(brush, size) : null)
using (var stroke = pen?.Brush != null ? CreatePaint(pen, size) : null)
{
MethodTable.Instance.DrawGeometry(Handle, impl.EffectivePath, fill != null ? fill.Brush : null,
stroke != null ? stroke.Brush : null, impl.FillRule == FillRule.EvenOdd);
if (fill != null)
{
Canvas.DrawPath(impl.EffectivePath, fill);
}
if (stroke != null)
{
Canvas.DrawPath(impl.EffectivePath, stroke);
}
}
}
unsafe NativeBrushContainer CreateBrush(IBrush brush, Size targetSize)
private SKPaint CreatePaint(IBrush brush, Size targetSize)
{
var rv = NativeBrushPool.Instance.Get();
rv.Brush->Opacity = brush.Opacity;
SKPaint paint = new SKPaint();
paint.IsStroke = false;
// TODO: SkiaSharp does not contain alpha yet!
//double opacity = brush.Opacity * _currentOpacity;
//paint.SetAlpha(paint.GetAlpha() * opacity);
paint.IsAntialias = true;
var solid = brush as SolidColorBrush;
if (solid != null)
{
rv.Brush->Type = NativeBrushType.Solid;
rv.Brush->Color = solid.Color.ToUint32();
return rv;
paint.Color = solid.Color.ToSKColor();
return paint;
}
var gradient = brush as GradientBrush;
if (gradient != null)
{
if (gradient.GradientStops.Count > NativeBrush.MaxGradientStops)
throw new NotSupportedException("Maximum supported gradient stop count is " +
NativeBrush.MaxGradientStops);
rv.Brush->GradientSpreadMethod = gradient.SpreadMethod;
rv.Brush->GradientStopCount = gradient.GradientStops.Count;
var tileMode = gradient.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradient.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradient.GradientStops.Select(s => (float)s.Offset).ToArray();
for (var c = 0; c < gradient.GradientStops.Count; c++)
var linearGradient = brush as LinearGradientBrush;
if (linearGradient != null)
{
var st = gradient.GradientStops[c];
rv.Brush->GradientStops[c] = (float) st.Offset;
rv.Brush->GradientStopColors[c] = st.Color.ToUint32();
var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly?
var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode);
paint.Shader = shader;
}
else
{
var radialGradient = brush as RadialGradientBrush;
if (radialGradient != null)
{
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)radialGradient.Radius;
}
// TODO: There is no SetAlpha in SkiaSharp
//paint.setAlpha(128);
var linearGradient = brush as LinearGradientBrush;
if (linearGradient != null)
{
rv.Brush->Type = NativeBrushType.LinearGradient;
rv.Brush->GradientStartPoint = linearGradient.StartPoint.ToPixels(targetSize);
rv.Brush->GradientEndPoint = linearGradient.EndPoint.ToPixels(targetSize);
}
var radialGradient = brush as RadialGradientBrush;
if (radialGradient != null)
{
rv.Brush->Type = NativeBrushType.RadialGradient;
rv.Brush->GradientStartPoint = radialGradient.Center.ToPixels(targetSize);
rv.Brush->GradientRadius = (float)radialGradient.Radius;
// would be nice to cache these shaders possibly?
var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode);
paint.Shader = shader;
}
}
return paint;
}
var tileBrush = brush as TileBrush;
if (tileBrush != null)
{
rv.Brush->Type = NativeBrushType.Image;
var helper = new TileBrushImplHelper(tileBrush, targetSize);
var bitmap = new BitmapImpl((int) helper.IntermediateSize.Width, (int) helper.IntermediateSize.Height);
rv.AddDisposable(bitmap);
using (var ctx = bitmap.CreateDrawingContext())
helper.DrawIntermediate(ctx);
rv.Brush->Bitmap = bitmap.Handle;
rv.Brush->BitmapTileMode = tileBrush.TileMode;
rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y);
// TODO: Get Tile Brushes working!!!
//
//throw new NotImplementedException();
// rv.Brush->Type = NativeBrushType.Image;
// var helper = new TileBrushImplHelper(tileBrush, targetSize);
// var bitmap = new BitmapImpl((int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height);
// rv.AddDisposable(bitmap);
// using (var ctx = bitmap.CreateDrawingContext())
// helper.DrawIntermediate(ctx);
// rv.Brush->Bitmap = bitmap.Handle;
// rv.Brush->BitmapTileMode = tileBrush.TileMode;
// rv.Brush->BitmapTranslation = new SkiaPoint(-helper.DestinationRect.X, -helper.DestinationRect.Y);
// SkMatrix matrix;
// matrix.setTranslate(brush->BitmapTranslation);
// SkShader::TileMode tileX = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode
// : (brush->BitmapTileMode == ptmFlipX || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode;
// SkShader::TileMode tileY = brush->BitmapTileMode == ptmNone ? SkShader::kClamp_TileMode
// : (brush->BitmapTileMode == ptmFlipY || brush->BitmapTileMode == ptmFlipXY) ? SkShader::kMirror_TileMode : SkShader::kRepeat_TileMode;
// paint.setShader(SkShader::CreateBitmapShader(brush->Bitmap->Bitmap, tileX, tileY, &matrix))->unref();
}
return rv;
return paint;
}
NativeBrushContainer CreateBrush(Pen pen, Size targetSize)
private SKPaint CreatePaint(Pen pen, Size targetSize)
{
var brush = CreateBrush(pen.Brush, targetSize);
brush.Brush->Stroke = true;
brush.Brush->StrokeThickness = (float)pen.Thickness;
brush.Brush->StrokeLineCap = pen.StartLineCap;
brush.Brush->StrokeLineJoin = pen.LineJoin;
brush.Brush->StrokeMiterLimit = (float)pen.MiterLimit;
if (pen.DashStyle?.Dashes != null)
{
var dashes = pen.DashStyle.Dashes;
if (dashes.Count > NativeBrush.MaxDashCount)
throw new NotSupportedException("Maximum supported dash count is " + NativeBrush.MaxDashCount);
brush.Brush->StrokeDashCount = dashes.Count;
for (int c = 0; c < dashes.Count; c++)
brush.Brush->StrokeDashes[c] = (float) dashes[c];
brush.Brush->StrokeDashOffset = (float)pen.DashStyle.Offset;
var paint = CreatePaint(pen.Brush, targetSize);
}
paint.IsStroke = true;
paint.StrokeWidth = (float)pen.Thickness;
if (pen.StartLineCap == PenLineCap.Round)
paint.StrokeCap = SKStrokeCap.Round;
else if (pen.StartLineCap == PenLineCap.Square)
paint.StrokeCap = SKStrokeCap.Square;
else
paint.StrokeCap = SKStrokeCap.Butt;
if (pen.LineJoin == PenLineJoin.Miter)
paint.StrokeJoin = SKStrokeJoin.Mitter;
else if (pen.LineJoin == PenLineJoin.Round)
paint.StrokeJoin = SKStrokeJoin.Round;
else
paint.StrokeJoin = SKStrokeJoin.Bevel;
paint.StrokeMiter = (float)pen.MiterLimit;
// TODO: Implement Dash Style support
//
//if (pen.DashStyle?.Dashes != null)
//{
// var dashes = pen.DashStyle.Dashes;
// if (dashes.Count > NativeBrush.MaxDashCount)
// throw new NotSupportedException("Maximum supported dash count is " + NativeBrush.MaxDashCount);
// brush.Brush->StrokeDashCount = dashes.Count;
// for (int c = 0; c < dashes.Count; c++)
// brush.Brush->StrokeDashes[c] = (float)dashes[c];
// brush.Brush->StrokeDashOffset = (float)pen.DashStyle.Offset;
//}
//if (brush->StrokeDashCount != 0)
//{
// paint.setPathEffect(SkDashPathEffect::Create(brush->StrokeDashes, brush->StrokeDashCount, brush->StrokeDashOffset))->unref();
//}
return brush;
return paint;
}
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0)
{
using (var brush = CreateBrush(pen, rect.Size))
using (var paint = CreatePaint(pen, rect.Size))
{
var rc = SkRect.FromRect(rect);
MethodTable.Instance.DrawRectangle(Handle, brush.Brush, ref rc, cornerRadius);
var rc = rect.ToSKRect();
if (cornerRadius == 0)
{
Canvas.DrawRect(rc, paint);
}
else
{
// TODO: DrawRRect (ore DrawRoundedRect) is not accesible in SkiaSharp yet. We should add that
// to SkiaSharp and initiate a PR....
Canvas.DrawRect(rc, paint);
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
}
}
}
public void FillRectangle(IBrush pbrush, Rect rect, float cornerRadius = 0)
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0)
{
using (var brush = CreateBrush(pbrush, rect.Size))
using (var paint = CreatePaint(brush, rect.Size))
{
var rc = SkRect.FromRect(rect);
MethodTable.Instance.DrawRectangle(Handle, brush.Brush, ref rc, cornerRadius);
var rc = rect.ToSKRect();
if (cornerRadius == 0)
{
Canvas.DrawRect(rc, paint);
}
else
{
// TODO: this does not exist in SkiaSharp yet
//throw new NotImplementedException();
//Canvas.DrawRoundedRect(rc, cornerRadius, cornerRadius, paint);
Canvas.DrawRect(rc, paint);
}
}
}
public void DrawText(IBrush foreground, Point origin, FormattedText text)
{
using (var br = CreateBrush(foreground, text.Measure()))
MethodTable.Instance.DrawFormattedText(Handle, br.Brush, ((FormattedTextImpl) text.PlatformImpl).Handle,
(float) origin.X, (float) origin.Y);
using (var paint = CreatePaint(foreground, text.Measure()))
{
var textImpl = text.PlatformImpl as FormattedTextImpl;
textImpl.Draw(this.Canvas, origin.ToSKPoint());
}
}
public void PushClip(Rect clip)
{
var rc = SkRect.FromRect(clip);
MethodTable.Instance.PushClip(Handle, ref rc);
Canvas.Save();
Canvas.ClipRect(clip.ToSKRect());
}
public void PopClip()
{
MethodTable.Instance.PopClip(Handle);
Canvas.Restore();
}
double _currentOpacity = 1.0f;
private readonly Stack<double> _opacityStack = new Stack<double>();
public void PushOpacity(double opacity)
{
_opacityStack.Push(_settings->Opacity);
_settings->Opacity *= opacity;
_opacityStack.Push(_currentOpacity);
_currentOpacity *= opacity;
}
public void PopOpacity()
{
_currentOpacity = _opacityStack.Pop();
}
public void PopOpacity() => _settings->Opacity = _opacityStack.Pop();
public virtual void Dispose()
{
}
private Matrix _currentTransform = Matrix.Identity;
@ -186,11 +264,12 @@ namespace Perspex.Skia
get { return _currentTransform; }
set
{
if(_currentTransform == value)
if (_currentTransform == value)
return;
_currentTransform = value;
MethodTable.Instance.SetTransform(Handle, value);
}
Canvas.SetMatrix(value.ToSKMatrix());
}
}
}
}

266
src/Skia/Perspex.Skia/FormattedTextImpl.cs

@ -4,31 +4,64 @@ using System.Runtime.InteropServices;
using System.Text;
using Perspex.Media;
using Perspex.Platform;
using SkiaSharp;
namespace Perspex.Skia
{
unsafe class FormattedTextImpl : PerspexHandleHolder, IFormattedTextImpl
unsafe class FormattedTextImpl : IFormattedTextImpl
{
private readonly NativeFormattedText* _shared;
private readonly string _text;
public SKPaint Paint { get; private set; }
public FormattedTextImpl(string text)
{
_text = text;
Paint = new SKPaint();
Paint.TextEncoding = SKTextEncoding.Utf16;
Paint.IsStroke = false;
Paint.IsAntialias = true;
LineOffset = 0;
// Replace 0 characters with zero-width spaces (200B)
_text = _text.Replace((char)0, (char)0x200B);
}
public static FormattedTextImpl Create(string text, string fontFamilyName, double fontSize, FontStyle fontStyle,
TextAlignment textAlignment, FontWeight fontWeight)
{
NativeFormattedText* pShared;
fixed (void* ptext = text)
{
IntPtr handle = MethodTable.Instance.CreateFormattedText(ptext, text.Length,
TypefaceCache.GetTypeface(fontFamilyName, fontStyle, fontWeight),
(float) fontSize, textAlignment, &pShared);
return new FormattedTextImpl(handle, pShared, text);
}
var typeface = TypefaceCache.GetTypeface(fontFamilyName, fontStyle, fontWeight);
FormattedTextImpl instance = new FormattedTextImpl(text);
instance.Paint.Typeface = typeface;
instance.Paint.TextSize = (float)fontSize;
instance.Paint.TextAlign = textAlignment.ToSKTextAlign();
instance.Rebuild();
return instance;
}
private readonly string _text;
readonly List<FormattedTextLine> _lines = new List<FormattedTextLine>();
readonly List<Rect> _rects = new List<Rect>();
List<PerspexFormattedTextLine> _skiaLines;
SKRect[] _skiaRects;
Size _size;
const float MAX_LINE_WIDTH = 10000;
float LineOffset;
float WidthConstraint = -1;
struct PerspexFormattedTextLine
{
public float Top;
public int Start;
public int Length;
public float Height;
public float Width;
};
public IEnumerable<FormattedTextLine> GetLines()
{
return _lines;
@ -46,13 +79,13 @@ namespace Perspex.Skia
{
IsInside = true,
TextPosition = c,
IsTrailing = (point.X - rc.X) > rc.Width/2
IsTrailing = (point.X - rc.X) > rc.Width / 2
};
}
}
bool end = point.X > _size.Width || point.Y > _size.Height;
return new TextHitTestResult() {IsTrailing = end, TextPosition = end ? _text.Length - 1 : 0};
return new TextHitTestResult() { IsTrailing = end, TextPosition = end ? _text.Length - 1 : 0 };
}
public Rect HitTestTextPosition(int index)
@ -75,36 +108,198 @@ namespace Perspex.Skia
public void SetForegroundBrush(IBrush brush, int startIndex, int length)
{
// TODO: we need an implementation here to properly support FormattedText
}
void Reload()
void Rebuild()
{
var length = _text.Length;
_lines.Clear();
_skiaRects = new SKRect[length];
_skiaLines = new List<PerspexFormattedTextLine>();
int curOff = 0;
float curY = 0;
var metrics = Paint.FontMetrics;
var mTop = metrics.Top; // The greatest distance above the baseline for any glyph (will be <= 0).
var mBottom = metrics.Bottom; // The greatest distance below the baseline for any glyph (will be >= 0).
var mLeading = metrics.Leading; // The recommended distance to add between lines of text (will be >= 0).
// This seems like the best measure of full vertical extent
float lineHeight = mBottom - mTop;
// Rendering is relative to baseline
LineOffset = -metrics.Top;
string subString;
byte[] bytes;
GCHandle pinnedArray;
IntPtr pointer;
for (int c = 0; curOff < length; c++)
{
float lineWidth = -1;
int measured;
int extraSkip = 0;
if (WidthConstraint <= 0)
{
measured = length;
}
else
{
float constraint = WidthConstraint;
if (constraint > MAX_LINE_WIDTH)
constraint = MAX_LINE_WIDTH;
subString = _text.Substring(curOff);
// TODO: This method is not linking into SkiaSharp so we must use the RAW buffer version for now
//measured = (int)Paint.BreakText(subString, constraint, out lineWidth) / 2;
bytes = Encoding.UTF8.GetBytes(subString);
pinnedArray = GCHandle.Alloc(bytes, GCHandleType.Pinned);
pointer = pinnedArray.AddrOfPinnedObject();
// for some reason I have to pass nBytes * 2. I assume under the hood it expects Unicode/WChar??
measured = (int)Paint.BreakText(pointer, (IntPtr)(bytes.Length * 2), constraint, out lineWidth) / 2;
pinnedArray.Free();
if (measured == 0)
{
measured = 1;
lineWidth = -1;
}
char nextChar = ' ';
if (curOff + measured < length)
nextChar = _text[curOff + measured];
if (nextChar != ' ')
{
// Perform scan for the last space and end the line there
for (int si = curOff + measured - 1; si > curOff; si--)
{
if (_text[si] == ' ')
{
measured = si - curOff;
extraSkip = 1;
break;
}
}
}
}
PerspexFormattedTextLine line = new PerspexFormattedTextLine();
line.Start = curOff;
line.Length = measured;
line.Width = lineWidth;
line.Height = lineHeight;
line.Top = curY;
if (line.Width < 0)
line.Width = _skiaRects[line.Start + line.Length - 1].Right;
// Build character rects
for (int i = line.Start; i < line.Start + line.Length; i++)
{
float prevRight = 0;
if (i != line.Start)
prevRight = _skiaRects[i - 1].Right;
subString = _text.Substring(line.Start, i - line.Start + 1);
float w = Paint.MeasureText(subString);
SKRect rc;
rc.Left = prevRight;
rc.Right = w;
rc.Top = line.Top;
rc.Bottom = line.Top + line.Height;
_skiaRects[i] = rc;
}
subString = _text.Substring(line.Start, line.Length);
line.Width = Paint.MeasureText(subString);
_skiaLines.Add(line);
curY += lineHeight;
// TODO: We may want to consider adding Leading to the vertical line spacing but for now
// it appears to make no difference. Revisit as part of FormattedText improvements.
//
//curY += mLeading;
curOff += measured + extraSkip;
}
// Now convert to Perspex data formats
_lines.Clear();
_rects.Clear();
float maxX = 0;
for (var c = 0; c < _shared->LineCount; c++)
for (var c = 0; c < _skiaLines.Count; c++)
{
var w = _shared->Lines[c].Width;
var w = _skiaLines[c].Width;
if (maxX < w)
maxX = w;
_lines.Add(new FormattedTextLine(_shared->Lines[c].Length, _shared->Lines[c].Height));
_lines.Add(new FormattedTextLine(_skiaLines[c].Length, _skiaLines[c].Height));
}
for (var c = 0; c < _text.Length; c++)
_rects.Add(_shared->Bounds[c].ToRect());
if (_shared->LineCount == 0)
{
_rects.Add(_skiaRects[c].ToPerspexRect());
}
if (_skiaLines.Count == 0)
{
_size = new Size();
}
else
{
var lastLine = _shared->Lines[_shared->LineCount - 1];
var lastLine = _skiaLines[_skiaLines.Count - 1];
_size = new Size(maxX, lastLine.Top + lastLine.Height);
}
}
void Rebuild()
internal void Draw(SKCanvas canvas, SKPoint origin)
{
MethodTable.Instance.RebuildFormattedText(Handle);
Reload();
SKPaint paint = Paint;
/* TODO: This originated from Native code, it might be useful for debugging character positions as
* we improve the FormattedText support. Will need to port this to C# obviously. Rmove when
* not needed anymore.
SkPaint dpaint;
ctx->Canvas->save();
ctx->Canvas->translate(origin.fX, origin.fY);
for (int c = 0; c < Lines.size(); c++)
{
dpaint.setARGB(255, 0, 0, 0);
SkRect rc;
rc.fLeft = 0;
rc.fTop = Lines[c].Top;
rc.fRight = Lines[c].Width;
rc.fBottom = rc.fTop + LineOffset;
ctx->Canvas->drawRect(rc, dpaint);
}
for (int c = 0; c < Length; c++)
{
dpaint.setARGB(255, c % 10 * 125 / 10 + 125, (c * 7) % 10 * 250 / 10, (c * 13) % 10 * 250 / 10);
dpaint.setStyle(SkPaint::kFill_Style);
ctx->Canvas->drawRect(Rects[c], dpaint);
}
ctx->Canvas->restore();
*/
for (int c = 0; c < _skiaLines.Count; c++)
{
PerspexFormattedTextLine line = _skiaLines[c];
var subString = _text.Substring(line.Start, line.Length);
canvas.DrawText(subString, origin.X, origin.Y + line.Top + LineOffset, paint);
}
}
Size _constraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
@ -114,32 +309,25 @@ namespace Perspex.Skia
get { return _constraint; }
set
{
if(_constraint == value)
if (_constraint == value)
return;
_constraint = value;
_shared->WidthConstraint = (_constraint.Width != double.PositiveInfinity)
? (float) _constraint.Width
_constraint = value;
WidthConstraint = (_constraint.Width != double.PositiveInfinity)
? (float)_constraint.Width
: -1;
Rebuild();
}
}
protected override void Delete(IntPtr handle)
{
MethodTable.Instance.DestroyFormattedText(handle);
}
public FormattedTextImpl(IntPtr handle, NativeFormattedText* shared, string text) : base(handle)
public override string ToString()
{
_shared = shared;
_text = text;
Reload();
return _text;
}
public override string ToString()
public void Dispose()
{
return _text;
}
}
}

238
src/Skia/Perspex.Skia/MethodTable.cs

@ -1,238 +0,0 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using Perspex.Media;
// ReSharper disable InconsistentNaming
namespace Perspex.Skia
{
unsafe abstract class MethodTable
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _CreateWindowRenderTarget(IntPtr nativeHandle);
public _CreateWindowRenderTarget CreateWindowRenderTarget;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _RenderTargetCreateRenderingContext(IntPtr target);
public _RenderTargetCreateRenderingContext RenderTargetCreateRenderingContext;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DisposeRenderTarget(IntPtr target);
public _DisposeRenderTarget DisposeRenderTarget;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DisposeRenderingContext(IntPtr ctx);
public _DisposeRenderingContext DisposeRenderingContext;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawRectangle(IntPtr ctx, void* brush, ref SkRect rect, float borderRadius);
public _DrawRectangle DrawRectangle;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _PushClip(IntPtr ctx, ref SkRect rect);
public _PushClip PushClip;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _PopClip(IntPtr ctx);
public _PopClip PopClip;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _SetTransform(IntPtr ctx, void* matrix6);
public _SetTransform SetTransformNative;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawLine(IntPtr ctx, void* brush, float x1, float y1, float x2, float y2);
public _DrawLine DrawLine;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _CreatePath(SkiaGeometryElement[] elements, int count, out SkRect bounds);
public _CreatePath CreatePath;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DisposePath(IntPtr handle);
public _DisposePath DisposePath;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _TransformPath(IntPtr path, void* matrix6);
public _TransformPath TransformPathNative;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawGeometry(IntPtr ctx, IntPtr path, void* fill, void* stroke, bool useEvenOdd);
public _DrawGeometry DrawGeometry;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DestroySkData(IntPtr handle);
public _DestroySkData DestroySkData;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool _LoadImage(byte[] data, int len, out IntPtr image, out int width, out int height);
public _LoadImage LoadImage;
public enum SkiaImageType
{
Png,Gif,Jpeg
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _SaveImage(IntPtr image, SkiaImageType type, int quality);
public _SaveImage SaveImage;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawImage(IntPtr ctx, IntPtr image, float opacity, ref SkRect srcRect, ref SkRect destRect);
public _DrawImage DrawImage;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _CreateRenderTargetBitmap(int width, int height);
public _CreateRenderTargetBitmap CreateRenderTargetBitmap;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DisposeImage(IntPtr image);
public _DisposeImage DisposeImage;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int _GetSkDataSize(IntPtr data);
public _GetSkDataSize GetSkDataSize;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _ReadSkData(IntPtr data, byte[] buffer, int count);
public _ReadSkData ReadSkData;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate NativeDrawingContextSettings* _GetDrawingContextSettingsPtr(IntPtr ctx);
public _GetDrawingContextSettingsPtr GetDrawingContextSettingsPtr;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _CreateTypeface(void* name, int style);
public _CreateTypeface CreateTypeface;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr _CreateFormattedText(void*utf16, int len, IntPtr typeface, float fontSize, TextAlignment align, NativeFormattedText** shared);
public _CreateFormattedText CreateFormattedText;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _RebuildFormattedText(IntPtr handle);
public _RebuildFormattedText RebuildFormattedText;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DestroyFormattedText(IntPtr handle);
public _DestroyFormattedText DestroyFormattedText;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _DrawFormattedText(IntPtr ctx, void* brush, IntPtr text, float x, float y);
public _DrawFormattedText DrawFormattedText;
public enum Option
{
ForceSoftware = 0
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void _SetOption(Option option, IntPtr value);
public _SetOption SetOption;
private static readonly Type[] TableOrder = new Type[]
{
typeof (_CreateWindowRenderTarget),
typeof (_DisposeRenderTarget),
typeof (_RenderTargetCreateRenderingContext),
typeof (_DisposeRenderingContext),
typeof (_DrawRectangle),
typeof (_PushClip),
typeof (_PopClip),
typeof (_SetTransform),
typeof (_DrawLine),
typeof (_CreatePath),
typeof (_DisposePath),
typeof (_DrawGeometry),
typeof (_GetSkDataSize),
typeof (_ReadSkData),
typeof (_DestroySkData),
typeof (_LoadImage),
typeof (_SaveImage),
typeof (_DrawImage),
typeof (_CreateRenderTargetBitmap),
typeof (_DisposeImage),
typeof (_GetDrawingContextSettingsPtr),
typeof (_CreateTypeface),
typeof (_CreateFormattedText),
typeof (_RebuildFormattedText),
typeof (_DestroyFormattedText),
typeof (_DrawFormattedText),
typeof (_SetOption),
typeof (_TransformPath)
};
void ConvertMatrix(Matrix value, float* target)
{
target[0] = (float)value.M11;
target[1] = (float)value.M21;
target[2] = (float)value.M31;
target[3] = (float)value.M12;
target[4] = (float)value.M22;
target[5] = (float)value.M32;
}
public unsafe IntPtr TransformPath(IntPtr path, Matrix matrix)
{
var tmp = stackalloc float[6];
ConvertMatrix(matrix, tmp);
return TransformPathNative(path, tmp);
}
public unsafe void SetTransform(IntPtr ctx, Matrix matrix)
{
var tmp = stackalloc float[6];
ConvertMatrix(matrix, tmp);
SetTransformNative(ctx, tmp);
}
protected MethodTable(IntPtr methodTable)
{
var dic = typeof (MethodTable).GetFields().ToDictionary(f => f.FieldType, f => f);
for (var c = 0; c < TableOrder.Length; c++)
{
IntPtr pMethod = Marshal.ReadIntPtr(methodTable, IntPtr.Size*c);
var t = TableOrder[c];
dic[t].SetValue(this, Marshal.GetDelegateForFunctionPointer(pMethod, t));
}
}
public static readonly MethodTable Instance = new MethodTableImpl();
}
}

113
src/Skia/Perspex.Skia/NativeBrush.cs

@ -1,113 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Perspex.Media;
namespace Perspex.Skia
{
internal enum NativeBrushType
{
Solid,
LinearGradient,
RadialGradient,
Image
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct NativeBrush
{
public const int MaxGradientStops = 1024;
public const int MaxDashCount = 1024;
public NativeBrushType Type;
public double Opacity;
public uint Color;
//Strokes
public bool Stroke;
public float StrokeThickness;
public PenLineJoin StrokeLineJoin;
public float StrokeMiterLimit;
public int StrokeDashCount;
public float StrokeDashOffset;
public PenLineCap StrokeLineCap;
//Gradients
public int GradientStopCount;
public GradientSpreadMethod GradientSpreadMethod;
public SkiaPoint GradientStartPoint, GradientEndPoint;
public float GradientRadius;
//Image Brush
public IntPtr Bitmap;
public TileMode BitmapTileMode;
public SkiaPoint BitmapTranslation;
//Blobs
public fixed uint GradientStopColors [MaxGradientStops];
public fixed float GradientStops [MaxGradientStops];
public fixed float StrokeDashes [MaxDashCount];
public void Reset()
{
Type = NativeBrushType.Solid;
Opacity = 1f;
Color = 0;
Stroke = false;
StrokeThickness = 1;
GradientStopCount = 0;
StrokeDashCount = 0;
StrokeLineCap = PenLineCap.Flat;
}
}
unsafe class NativeBrushContainer : IDisposable
{
private readonly NativeBrushPool _pool;
public NativeBrush* Brush;
readonly List<IDisposable> _disposables = new List<IDisposable>();
public NativeBrushContainer(NativeBrushPool pool)
{
_pool = pool;
Brush = (NativeBrush*) Marshal.AllocHGlobal(Marshal.SizeOf(typeof (NativeBrush))).ToPointer();
Brush->Reset();
}
public void AddDisposable(IDisposable disp)
{
_disposables.Add(disp);
}
public void Dispose()
{
foreach (var disp in _disposables)
disp.Dispose();
_disposables.Clear();
Brush->Reset();
_pool?.Return(this);
}
}
class NativeBrushPool
{
public static NativeBrushPool Instance { get; } = new NativeBrushPool();
readonly Stack<NativeBrushContainer> _pool = new Stack<NativeBrushContainer>();
public void Return(NativeBrushContainer c)
{
_pool.Push(c);
}
public NativeBrushContainer Get()
{
if (_pool.Count == 0)
return new NativeBrushContainer(this);
return _pool.Pop();
}
}
}

10
src/Skia/Perspex.Skia/NativeDrawingContextSettings.cs

@ -1,10 +0,0 @@
using System.Runtime.InteropServices;
namespace Perspex.Skia
{
[StructLayout(LayoutKind.Sequential)]
struct NativeDrawingContextSettings
{
public double Opacity;
}
}

23
src/Skia/Perspex.Skia/NativeFormattedText.cs

@ -1,23 +0,0 @@
using System.Runtime.InteropServices;
namespace Perspex.Skia
{
[StructLayout(LayoutKind.Sequential)]
unsafe struct NativeFormattedText
{
public float WidthConstraint;
public int LineCount;
public NativeFormattedTextLine* Lines;
public SkRect* Bounds;
};
[StructLayout(LayoutKind.Sequential)]
unsafe struct NativeFormattedTextLine
{
public float Top;
public int Start;
public int Length;
public float Height;
public float Width;
};
}

12
src/Skia/Perspex.Skia/Perspex.Skia.projitems

@ -12,17 +12,15 @@
<Compile Include="$(MSBuildThisFileDirectory)BitmapImpl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)DrawingContextImpl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)FormattedTextImpl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MethodTable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NativeBrush.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NativeDrawingContextSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NativeFormattedText.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PerspexHandleHolder.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PlatformRenderInterface.cs" />
<Compile Include="$(MSBuildThisFileDirectory)RenderTarget.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SkiaPlatform.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SkiaPoint.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SkRect.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SkiaSharpExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)StreamGeometryImpl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TypefaceCache.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WindowDrawingContextImpl.cs" />
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)readme.md" />
</ItemGroup>
</Project>

106
src/Skia/Perspex.Skia/PerspexHandleHolder.cs

@ -1,106 +0,0 @@
using System;
namespace Perspex.Skia
{
abstract class PerspexHandleHolder : IDisposable
{
private readonly IntPtr _handle;
public IntPtr Handle
{
get
{
CheckDisposed();
return _handle;
}
}
public bool IsDisposed { get; private set; }
public void CheckDisposed()
{
if (IsDisposed)
throw new ObjectDisposedException(GetType().FullName);
}
protected PerspexHandleHolder(IntPtr handle)
{
_handle = handle;
}
protected abstract void Delete(IntPtr handle);
public void Dispose()
{
if(IsDisposed)
return;
IsDisposed = true;
Delete(_handle);
GC.SuppressFinalize(this);
}
~PerspexHandleHolder()
{
Dispose();
}
}
class RefCountable<T> : IDisposable where T : PerspexHandleHolder
{
class Shared
{
public readonly T Target;
private int _refCount = 1;
public Shared(T target)
{
Target = target;
}
public void AddRef() => _refCount++;
public void Release()
{
_refCount--;
if (_refCount <= 0)
Target.Dispose();
}
}
public bool IsDisposed => _shared == null;
private Shared _shared;
public void CheckDisposed()
{
if (IsDisposed)
throw new ObjectDisposedException(GetType().FullName);
}
public IntPtr Handle
{
get
{
CheckDisposed();
return _shared.Target.Handle;
}
}
public RefCountable(T handle)
{
_shared = new Shared(handle);
}
public RefCountable(RefCountable<T> other)
{
other._shared.Target.CheckDisposed();
other._shared.AddRef();
_shared = other._shared;
}
public RefCountable<T> Clone() => new RefCountable<T>(this);
public void Dispose()
{
_shared?.Release();
_shared = null;
}
}
}

19
src/Skia/Perspex.Skia/PlatformRenderInterface.cs

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Perspex.Media;
using Perspex.Platform;
using SkiaSharp;
namespace Perspex.Skia
{
@ -29,12 +30,13 @@ namespace Perspex.Skia
IBitmapImpl LoadBitmap(byte[] data)
{
IntPtr ptr;
int width;
int height;
if (!MethodTable.Instance.LoadImage(data, data.Length, out ptr, out width, out height))
var bitmap = new SKBitmap();
if (!SKImageDecoder.DecodeMemory(data, bitmap))
{
throw new ArgumentException("Unable to load bitmap from provided data");
return new BitmapImpl(ptr, width, height);
}
return new BitmapImpl(bitmap);
}
public IBitmapImpl LoadBitmap(System.IO.Stream stream)
@ -56,10 +58,13 @@ namespace Perspex.Skia
throw new ArgumentException("Width can't be less than 1", nameof(width));
if (height < 1)
throw new ArgumentException("Height can't be less than 1", nameof(height));
return new BitmapImpl(width, height);
}
public IRenderTarget CreateRenderer(IPlatformHandle handle)
=> new RenderTarget(handle);
public IRenderTarget CreateRenderer(IPlatformHandle handle)
{
return new WindowRenderTarget(handle.Handle);
}
}
}

174
src/Skia/Perspex.Skia/RenderTarget.cs

@ -4,24 +4,184 @@ using System.Runtime.InteropServices;
using System.Text;
using Perspex.Media;
using Perspex.Platform;
using SkiaSharp;
// TODO: I'm not sure the best way to bring in the platform specific rendering
//
#if __IOS__
using CoreGraphics;
using UIKit;
#elif WIN32
using Perspex.Win32.Interop;
#endif
namespace Perspex.Skia
{
#if !__ANDROID__
class RenderTarget : PerspexHandleHolder, IRenderTarget
internal partial class RenderTarget : IRenderTarget
{
public RenderTarget(IPlatformHandle handle) : base(MethodTable.Instance.CreateWindowRenderTarget(handle.Handle))
public SKSurface Surface { get; protected set; }
public virtual DrawingContext CreateDrawingContext()
{
return
new DrawingContext(
new DrawingContextImpl(Surface.Canvas));
}
public void Dispose()
{
// Nothing to do here.
}
}
internal class WindowRenderTarget : RenderTarget
{
private readonly IntPtr _hwnd;
SKBitmap _bitmap;
int Width { get; set; }
int Height { get; set; }
public WindowRenderTarget(IntPtr hwnd)
{
_hwnd = hwnd;
FixSize();
}
#if __IOS__
private CGRect GetApplicationFrame()
{
// if we are excluding Status Bar then we use ApplicationFrame
// otherwise we use full screen bounds. Note that this must also match
// the Skia/PerspexView!!!
//
bool excludeStatusArea = false; // TODO: make this configurable later
if (excludeStatusArea)
{
return UIScreen.MainScreen.ApplicationFrame;
}
else
{
return UIScreen.MainScreen.Bounds;
}
}
#endif
private void FixSize()
{
int width, height;
GetPlatformWindowSize(_hwnd, out width, out height);
if (Width == width && Height == height)
return;
Width = width;
Height = height;
if (Surface != null)
{
Surface.Dispose();
}
if (_bitmap != null)
{
_bitmap.Dispose();
}
_bitmap = new SKBitmap(width, height, SKColorType.N_32, SKAlphaType.Premul);
IntPtr length;
var pixels = _bitmap.GetPixels(out length);
// Wrap the bitmap in a Surface and keep it cached
Surface = SKSurface.Create(_bitmap.Info, pixels, _bitmap.RowBytes);
}
protected override void Delete(IntPtr handle) => MethodTable.Instance.DisposeRenderTarget(handle);
private void GetPlatformWindowSize(IntPtr hwnd, out int w, out int h)
{
#if __IOS__
var bounds = GetApplicationFrame();
w = (int)bounds.Width;
h = (int)bounds.Height;
#elif WIN32
UnmanagedMethods.RECT rc;
UnmanagedMethods.GetClientRect(_hwnd, out rc);
w = rc.right - rc.left;
h = rc.bottom - rc.top;
#else
throw new NotImplementedException();
#endif
}
public DrawingContext CreateDrawingContext()
public override DrawingContext CreateDrawingContext()
{
FixSize();
var canvas = Surface.Canvas;
canvas.RestoreToCount(0);
canvas.Save();
#if __IOS__
var screenScale = UIScreen.MainScreen.Scale;
canvas.Scale((float)screenScale, (float)screenScale);
#endif
canvas.Clear(SKColors.Red);
canvas.ResetMatrix();
return
new DrawingContext(
new DrawingContextImpl(MethodTable.Instance.RenderTargetCreateRenderingContext(Handle)));
new WindowDrawingContextImpl(this));
}
}
public void Present()
{
_bitmap.LockPixels();
IntPtr length;
var pixels = _bitmap.GetPixels(out length);
#if __IOS__
const int bitmapInfo = ((int)CGBitmapFlags.ByteOrder32Big) | ((int)CGImageAlphaInfo.PremultipliedLast);
var bounds = GetApplicationFrame();
var statusBarOffset = UIScreen.MainScreen.Bounds.Height - bounds.Height;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(pixels, _bitmap.Width, _bitmap.Height, 8, _bitmap.Width * 4, colorSpace, (CGImageAlphaInfo)bitmapInfo))
using (var image = bContext.ToImage())
using (var context = UIGraphics.GetCurrentContext())
{
// flip the image for CGContext.DrawImage
context.TranslateCTM(0, bounds.Height + statusBarOffset);
context.ScaleCTM(1, -1);
context.DrawImage(bounds, image);
}
#elif WIN32
UnmanagedMethods.BITMAPINFO bmi = new UnmanagedMethods.BITMAPINFO();
bmi.biSize = UnmanagedMethods.SizeOf_BITMAPINFOHEADER;
bmi.biWidth = _bitmap.Width;
bmi.biHeight = -_bitmap.Height; // top-down image
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biCompression = (uint)UnmanagedMethods.BitmapCompressionMode.BI_RGB;
bmi.biSizeImage = 0;
IntPtr hdc = UnmanagedMethods.GetDC(_hwnd);
int ret = UnmanagedMethods.SetDIBitsToDevice(hdc,
0, 0,
(uint)_bitmap.Width, (uint)_bitmap.Height,
0, 0,
0, (uint)_bitmap.Height,
pixels,
ref bmi,
(uint)UnmanagedMethods.DIBColorTable.DIB_RGB_COLORS);
UnmanagedMethods.ReleaseDC(_hwnd, hdc);
#endif
_bitmap.UnlockPixels();
}
}
}

26
src/Skia/Perspex.Skia/SkRect.cs

@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Perspex.Skia
{
[StructLayout(LayoutKind.Sequential)]
struct SkRect
{
public float Left, Top, Right, Bottom;
public static SkRect FromRect(Rect rc)
{
return new SkRect()
{
Left = (float) rc.X,
Top = (float) rc.Y,
Right = (float) rc.Right,
Bottom = (float) rc.Bottom
};
}
public Rect ToRect() => new Rect(Left, Top, Right - Left, Bottom - Top);
}
}

6
src/Skia/Perspex.Skia/SkiaPlatform.cs

@ -30,7 +30,11 @@ namespace Perspex.Skia
set
{
s_forceSoftwareRendering = value;
MethodTable.Instance.SetOption(MethodTable.Option.ForceSoftware, new IntPtr(value ? 1 : 0));
// TODO: I left this property here as place holder. Do we still need the ability to Force software rendering?
// Is it even possible with SkiaSharp? Perhaps kekekes can answer as part of the HW accel work.
//
throw new NotImplementedException();
}
}
}

29
src/Skia/Perspex.Skia/SkiaPoint.cs

@ -1,29 +0,0 @@
using System.Runtime.InteropServices;
namespace Perspex.Skia
{
[StructLayout(LayoutKind.Sequential)]
struct SkiaPoint
{
public float X, Y;
public SkiaPoint(float x, float y)
{
X = x;
Y = y;
}
public SkiaPoint(double x, double y)
{
X = (float)x;
Y = (float)y;
}
public SkiaPoint(Point p) : this(p.X, p.Y)
{
}
public static implicit operator SkiaPoint(Point pt) => new SkiaPoint(pt);
}
}

69
src/Skia/Perspex.Skia/SkiaSharpExtensions.cs

@ -0,0 +1,69 @@
using Perspex.Media;
using SkiaSharp;
namespace Perspex.Skia
{
public static class SkiaSharpExtensions
{
public static SKPoint ToSKPoint(this Point p)
{
return new SKPoint((float)p.X, (float)p.Y);
}
public static SKRect ToSKRect(this Rect r)
{
return new SKRect((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);
}
public static Rect ToPerspexRect(this SKRect r)
{
return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
}
public static SKMatrix ToSKMatrix(this Matrix m)
{
var sm = new SKMatrix
{
ScaleX = (float)m.M11,
SkewX = (float)m.M21,
TransX = (float)m.M31,
SkewY = (float)m.M12,
ScaleY = (float)m.M22,
TransY = (float)m.M32,
Persp0 = 0,
Persp1 = 0,
Persp2 = 1
};
return sm;
}
public static SKColor ToSKColor(this Media.Color c)
{
return new SKColor(c.R, c.G, c.B, c.A);
}
public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m)
{
switch (m)
{
default:
case Media.GradientSpreadMethod.Pad: return SKShaderTileMode.Clamp;
case Media.GradientSpreadMethod.Reflect: return SKShaderTileMode.Mirror;
case Media.GradientSpreadMethod.Repeat: return SKShaderTileMode.Repeat;
}
}
public static SKTextAlign ToSKTextAlign(this TextAlignment a)
{
switch (a)
{
default:
case TextAlignment.Left: return SKTextAlign.Left;
case TextAlignment.Center: return SKTextAlign.Center;
case TextAlignment.Right: return SKTextAlign.Right;
}
}
}
}

135
src/Skia/Perspex.Skia/StreamGeometryImpl.cs

@ -6,46 +6,18 @@ using System.Text;
using Perspex.Media;
using Perspex.Platform;
using Perspex.RenderHelpers;
using SkiaSharp;
namespace Perspex.Skia
{
enum SkiaGeometryElementType
{
LineTo,
QuadTo,
BezierTo,
BeginFigure,
EndFigure
};
[StructLayout(LayoutKind.Sequential)]
struct SkiaGeometryElement
{
public SkiaGeometryElementType Type;
public SkiaPoint Point1, Point2, Point3;
public bool Flag;
}
class SkPath : PerspexHandleHolder
{
public SkPath(IntPtr handle) : base(handle)
{
}
protected override void Delete(IntPtr handle)
{
MethodTable.Instance.DisposePath(handle);
}
}
class StreamGeometryImpl : IStreamGeometryImpl
{
RefCountable<SkPath> _path;
RefCountable<SkPath> _transformedPath;
SKPath _path;
SKPath _transformedPath;
private Matrix _transform = Matrix.Identity;
public IntPtr EffectivePath => (_transformedPath ?? _path).Handle;
public SKPath EffectivePath => (_transformedPath ?? _path);
public Rect GetRenderBounds(double strokeThickness)
{
@ -60,8 +32,9 @@ namespace Perspex.Skia
get { return _transform; }
set
{
if(_transform == value)
if (_transform == value)
return;
_transform = value;
ApplyTransform();
}
@ -69,108 +42,108 @@ namespace Perspex.Skia
void ApplyTransform()
{
if(_path == null)
if (_path == null)
return;
if (_transformedPath != null)
{
_transformedPath.Dispose();
_transformedPath = null;
}
if (!_transform.IsIdentity)
_transformedPath =
new RefCountable<SkPath>(new SkPath(MethodTable.Instance.TransformPath(_path.Handle, Transform)));
// TODO: SkiaSharp does not expose Transform yet!!!
//if (!Transform.IsIdentity)
//{
// _transformedPath = new SKPath();
// _path.Transform(Transform.ToSKMatrix(), _transformedPath);
//}
}
public IStreamGeometryImpl Clone()
{
return new StreamGeometryImpl {_path = _path?.Clone(), _transformedPath = _transformedPath?.Clone(), _transform = Transform, Bounds = Bounds};
// TODO: there is no SKPath.Clone yet!!!!!!!!!!!!!
//
//return new StreamGeometryImpl
//{
// _path = _path?.Clone(),
// _transformedPath = _transformedPath?.Clone(),
// _transform = Transform,
// Bounds = Bounds
//};
return this; // this will probably end bad!!
}
public IStreamGeometryContextImpl Open()
{
_path = new SKPath();
return new StreamContext(this);
}
class StreamContext : IStreamGeometryContextImpl
{
private readonly StreamGeometryImpl _geometryImpl;
readonly List<SkiaGeometryElement> _elements = new List<SkiaGeometryElement>();
private SKPath _path;
Point _currentPoint;
public StreamContext(StreamGeometryImpl geometryImpl)
{
_geometryImpl = geometryImpl;
_path = _geometryImpl._path;
}
public void Dispose()
{
var arr = _elements.ToArray();
SkRect rc;
_geometryImpl._path?.Dispose();
_geometryImpl._path =
new RefCountable<SkPath>(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)));
// TODO: Not sure what we need to do here. This code left here for reference.
//
// var arr = _elements.ToArray();
// SkRect rc;
// _path?.Dispose();
// _path = new SKPath(new SkPath(MethodTable.Instance.CreatePath(arr, arr.Length, out rc)));
// _geometryImpl.ApplyTransform();
// _geometryImpl.Bounds = rc.ToRect();
SKRect rc;
_path.GetBounds(out rc);
_geometryImpl.ApplyTransform();
_geometryImpl.Bounds = rc.ToRect();
_geometryImpl.Bounds = rc.ToPerspexRect();
}
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
ArcToHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
_currentPoint = point;
throw new NotImplementedException();
}
public void BeginFigure(Point startPoint, bool isFilled)
{
_elements.Add(new SkiaGeometryElement
{
Type = SkiaGeometryElementType.BeginFigure,
Point1 = new SkiaPoint(startPoint),
Flag = isFilled
});
_path.MoveTo((float)startPoint.X, (float)startPoint.Y);
_currentPoint = startPoint;
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
_elements.Add(new SkiaGeometryElement
{
Type = SkiaGeometryElementType.BezierTo,
Point1 = new SkiaPoint(point1),
Point2 = new SkiaPoint(point2),
Point3 = new SkiaPoint(point3)
});
_path.CubicTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y, (float)point3.X, (float)point3.Y);
_currentPoint = point3;
}
public void QuadraticBezierTo(Point control, Point endPoint)
public void QuadraticBezierTo(Point point1, Point point2)
{
_elements.Add(new SkiaGeometryElement
{
Type = SkiaGeometryElementType.QuadTo,
Point1 = control,
Point2 = endPoint
});
_currentPoint = endPoint;
_path.QuadTo((float)point1.X, (float)point1.Y, (float)point2.X, (float)point2.Y);
_currentPoint = point2;
}
public void LineTo(Point point)
{
_elements.Add(new SkiaGeometryElement
{
Type = SkiaGeometryElementType.LineTo,
Point1 = new SkiaPoint(point)
});
_path.LineTo((float)point.X, (float)point.Y);
_currentPoint = point;
}
public void EndFigure(bool isClosed)
{
_elements.Add(new SkiaGeometryElement
if (isClosed)
{
Type = SkiaGeometryElementType.EndFigure,
Flag = isClosed
});
_path.Close();
}
}
public void SetFillRule(FillRule fillRule)

48
src/Skia/Perspex.Skia/TypefaceCache.cs

@ -2,49 +2,45 @@
using System.Collections.Generic;
using System.Text;
using Perspex.Media;
using SkiaSharp;
namespace Perspex.Skia
{
static class TypefaceCache
{
static readonly Dictionary<string, Dictionary<Style, IntPtr>>Cache = new Dictionary<string, Dictionary<Style, IntPtr>>();
unsafe static IntPtr GetTypeface(string name, Style style)
static readonly Dictionary<string, Dictionary<SKTypefaceStyle, SKTypeface>> Cache = new Dictionary<string, Dictionary<SKTypefaceStyle, SKTypeface>>();
unsafe static SKTypeface GetTypeface(string name, SKTypefaceStyle style)
{
if (name == null)
name = "Arial";
Dictionary<Style, IntPtr> entry;
Dictionary<SKTypefaceStyle, SKTypeface> entry;
if (!Cache.TryGetValue(name, out entry))
Cache[name] = entry = new Dictionary<Style, IntPtr>();
IntPtr rv;
if (!entry.TryGetValue(style, out rv))
Cache[name] = entry = new Dictionary<SKTypefaceStyle, SKTypeface>();
SKTypeface typeface = null;
if (!entry.TryGetValue(style, out typeface))
{
var bytes = Encoding.ASCII.GetBytes(name);
var buffer = new byte[bytes.Length + 1];
bytes.CopyTo(buffer, 0);
fixed (void* pname = buffer)
typeface = SKTypeface.FromFamilyName(name, style);
if (typeface == null)
{
entry[style] = rv = MethodTable.Instance.CreateTypeface(pname, (int)style);
typeface = SKTypeface.FromFamilyName(null, style);
}
entry[style] = typeface;
}
return rv;
return typeface;
}
[Flags]
enum Style
public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
{
Normal = 0,
Bold = 0x01,
Italic = 0x02,
BoldItalic = 0x03
};
public static IntPtr GetTypeface(string name, FontStyle style, FontWeight weight)
{
Style sstyle = Style.Normal;
SKTypefaceStyle sstyle = SKTypefaceStyle.Normal;
if (style != FontStyle.Normal)
sstyle |= Style.Italic;
if(weight>FontWeight.Normal)
sstyle |= Style.Bold;
sstyle |= SKTypefaceStyle.Italic;
if (weight > FontWeight.Normal)
sstyle |= SKTypefaceStyle.Bold;
return GetTypeface(name, sstyle);
}

21
src/Skia/Perspex.Skia/WindowDrawingContextImpl.cs

@ -0,0 +1,21 @@

namespace Perspex.Skia
{
// not sure we need this yet
internal class WindowDrawingContextImpl : DrawingContextImpl
{
WindowRenderTarget _target;
public WindowDrawingContextImpl(WindowRenderTarget target)
: base(target.Surface.Canvas)
{
_target = target;
}
public override void Dispose()
{
base.Dispose();
_target.Present();
}
}
}

46
src/Skia/Perspex.Skia/readme.md

@ -0,0 +1,46 @@
TODO:
BitmapImpl
- constructor from Width/Height
- Save
DrawingContextImpl
- DrawRoundRect is not properly implemented due to lack of support in SkiaSharp
- Alpha support missing as SkiaSharp does not expose this
- Gradient Shader caching?
- TileBrushes
- Pen Dash styles
Formatted Text Rendering
- minor polish
RenderTarget
- Figure out a cleaner implementation across all platforms
- HW acceleration
StreamGeometry
- Paths within Paths may not work right
- Paths cannot be Cloned (lack of SkiaSupport)
- Paths cannot be transformed (lack of SkiaSupport)
- ArcTo
App Bootstrapping
- Cleanup the testapplications across all platforms
- Add a cleaner Fluent API for the subsystems
- ie. app.UseDirect2D() (via platform specific extension methods)
Android
- Not tested at all yet
iOS
- Get GLView working again. See HW above
Win32
- Cleanup the unmanaged methods (BITMAPINFO) if possible
General
- Cleanup/eliminate obsolete files
- Finish cleanup of the many Test Applications
- Get Skia Unit Tests passing

67
src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs

@ -532,7 +532,72 @@ namespace Perspex.Win32.Interop
WM_DISPATCH_WORK_ITEM = WM_USER,
}
[DllImport("user32.dll", SetLastError = true)]
public enum BitmapCompressionMode : uint
{
BI_RGB = 0,
BI_RLE8 = 1,
BI_RLE4 = 2,
BI_BITFIELDS = 3,
BI_JPEG = 4,
BI_PNG = 5
}
public enum DIBColorTable
{
DIB_RGB_COLORS = 0, /* color table in RGBs */
DIB_PAL_COLORS /* color table in palette indices */
};
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{
public byte rgbBlue;
public byte rgbGreen;
public byte rgbRed;
public byte rgbReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO
{
// C# cannot inlay structs in structs so must expand directly here
//
//[StructLayout(LayoutKind.Sequential)]
//public struct BITMAPINFOHEADER
//{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public BitmapCompressionMode biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;
//}
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public uint[] cols;
}
public const int SizeOf_BITMAPINFOHEADER = 40;
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("gdi32.dll")]
public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest,
uint dwWidth, uint dwHeight,
int XSrc, int YSrc,
uint uStartScan, uint cScanLines,
IntPtr lpvBits, [In] ref BITMAPINFO lpbmi, uint fuColorUse);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle, bool bMenu, uint dwExStyle);
[DllImport("user32.dll")]

21
src/Windows/Perspex.Win32/SystemDialogImpl.cs

@ -40,20 +40,23 @@ namespace Perspex.Win32
var filterBuffer = new char[filters.Length];
filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length);
var defExt = (dialog as SaveFileDialog)?.DefaultExtension;
var defExt = (dialog as SaveFileDialog)?.DefaultExtension.ToArray();
var fileBuffer = new char[256];
dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
string userSelectedExt = null;
var title = dialog.Title.ToArray();
var initialDir = dialog.InitialDirectory.ToArray();
fixed (char* pFileBuffer = fileBuffer)
fixed (char* pFilterBuffer = filterBuffer)
fixed (char* pDefExt = defExt)
fixed (char* pInitDir = dialog.InitialDirectory)
fixed (char* pTitle = dialog.Title)
fixed (char* pInitDir = initialDir)
fixed (char* pTitle = title)
{
var ofn = new UnmanagedMethods.OpenFileName()
{
{
hwndOwner = hWnd,
hInstance = IntPtr.Zero,
lCustData = IntPtr.Zero,
@ -121,8 +124,8 @@ namespace Perspex.Win32
!userSelectedExt.Contains("*"))
dir = Path.ChangeExtension(dir, userSelectedExt);
}
return new[] {dir};
return new[] { dir };
}
return files.Select(f => Path.Combine(dir, f)).ToArray();
@ -130,7 +133,7 @@ namespace Perspex.Win32
}
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
{
return Task.Factory.StartNew(() =>
{
string result = string.Empty;
@ -174,7 +177,7 @@ namespace Perspex.Win32
{
try
{
result = Marshal.PtrToStringAuto(pszString);
result = Marshal.PtrToStringAuto(pszString);
}
finally
{

3
src/iOS/Perspex.iOS/Perspex.iOS.csproj

@ -37,12 +37,13 @@
<Compile Include="Clipboard.cs" />
<Compile Include="CursorFactory.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="iOSPlatform.cs" />
<Compile Include="PerspexView.cs" />
<Compile Include="PerspexAppDelegate.cs" />
<Compile Include="PlatformSettings.cs" />
<Compile Include="PlatformThreadingInterface.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Specific\KeyboardEventsHelper.cs" />
<Compile Include="WindowingPlatformImpl.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Animation\Perspex.Animation.csproj">

75
src/iOS/Perspex.iOS/PerspexAppDelegate.cs

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Perspex.Controls.Platform;
using Perspex.Input;
using Perspex.Input.Platform;
using Perspex.Platform;
using Perspex.Shared.PlatformSupport;
using Perspex.Skia;
using UIKit;
namespace Perspex.iOS
{
public class PerspexAppDelegate : UIApplicationDelegate
{
static bool _initialized = false;
internal static MouseDevice MouseDevice;
internal static KeyboardDevice KeyboardDevice;
protected void InitPerspex(Type appType)
{
if(_initialized)
return;
_initialized = true;
var window = new UIWindow(UIScreen.MainScreen.Bounds);
var controller = new PerspexViewController(window);
window.RootViewController = controller;
window.MakeKeyAndVisible();
Application.RegisterPlatformCallback(() =>
{
MouseDevice = new MouseDevice();
KeyboardDevice = new KeyboardDevice();
SharedPlatform.Register(appType.Assembly);
PerspexLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<Clipboard>()
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatform(controller.PerspexView));
SkiaPlatform.Initialize();
});
}
class WindowingPlatform : IWindowingPlatform
{
private readonly IWindowImpl _window;
public WindowingPlatform(IWindowImpl window)
{
_window = window;
}
public IWindowImpl CreateWindow()
{
return _window;
}
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public IPopupImpl CreatePopup()
{
throw new NotImplementedException();
}
}
}
}

27
src/iOS/Perspex.iOS/PerspexView.cs

@ -57,6 +57,7 @@ namespace Perspex.iOS
(_controller.InterfaceOrientation == UIInterfaceOrientation.LandscapeLeft
|| _controller.InterfaceOrientation == UIInterfaceOrientation.LandscapeRight);
// Bounds here (if top level) needs to correspond with the rendertarget
var frame = UIScreen.MainScreen.Bounds;
if (needFlip)
Frame = new CGRect(frame.Y, frame.X, frame.Height, frame.Width);
@ -74,7 +75,15 @@ namespace Perspex.iOS
public IPlatformHandle Handle => PerspexPlatformHandle;
public double Scaling => 1;
public double Scaling
{
get
{
// This does not appear to make any difference, but on iOS we
// have Retina (x2) and we probably want this eventually
return 1; //UIScreen.MainScreen.Scale;
}
}
public WindowState WindowState
{
@ -159,8 +168,8 @@ namespace Perspex.iOS
var location = touch.LocationInView(this).ToPerspex();
Input?.Invoke(new RawMouseEventArgs(
PerspexAppDelegate.MouseDevice,
(uint) touch.Timestamp,
iOSPlatform.MouseDevice,
(uint)touch.Timestamp,
_inputRoot,
RawMouseEventType.LeftButtonUp,
location,
@ -176,10 +185,10 @@ namespace Perspex.iOS
{
var location = touch.LocationInView(this).ToPerspex();
_touchLastPoint = location;
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot,
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.None));
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot,
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.LeftButtonDown, location, InputModifiers.None));
}
}
@ -190,16 +199,16 @@ namespace Perspex.iOS
if (touch != null)
{
var location = touch.LocationInView(this).ToPerspex();
if (PerspexAppDelegate.MouseDevice.Captured != null)
Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot,
if (iOSPlatform.MouseDevice.Captured != null)
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.LeftMouseButton));
else
{
//magic number based on test - correction of 0.02 is working perfect
double correction = 0.02;
Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp,
_inputRoot, location, (location - _touchLastPoint)* correction, InputModifiers.LeftMouseButton));
Input?.Invoke(new RawMouseWheelEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp,
_inputRoot, location, (location - _touchLastPoint) * correction, InputModifiers.LeftMouseButton));
}
_touchLastPoint = location;
}

33
src/iOS/Perspex.iOS/WindowingPlatformImpl.cs

@ -0,0 +1,33 @@
using Perspex.Platform;
using System;
namespace Perspex.iOS
{
// This is somewhat generic, could probably put this elsewhere. But I don't think
// it should part of the iOS App Delegate
//
class WindowingPlatformImpl : IWindowingPlatform
{
private readonly IWindowImpl _window;
public WindowingPlatformImpl(IWindowImpl window)
{
_window = window;
}
public IWindowImpl CreateWindow()
{
return _window;
}
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public IPopupImpl CreatePopup()
{
throw new NotImplementedException();
}
}
}

74
src/iOS/Perspex.iOS/iOSPlatform.cs

@ -0,0 +1,74 @@
using System;
using System.Reflection;
using Perspex.Input;
using Perspex.Input.Platform;
using Perspex.iOS;
using Perspex.Platform;
using Perspex.Shared.PlatformSupport;
using Perspex.Skia;
using UIKit;
namespace Perspex
{
public static class iOSApplicationExtensions
{
public static AppT UseiOS<AppT>(this AppT app) where AppT : Application
{
Perspex.iOS.iOSPlatform.Initialize();
return app;
}
// TODO: Can we merge this with UseSkia somehow once HW/platform cleanup is done?
public static AppT UseSkiaViewHost<AppT>(this AppT app) where AppT : Application
{
var window = new UIWindow(UIScreen.MainScreen.Bounds);
var controller = new PerspexViewController(window);
window.RootViewController = controller;
window.MakeKeyAndVisible();
PerspexLocator.CurrentMutable
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformImpl(controller.PerspexView));
SkiaPlatform.Initialize();
return app;
}
public static AppT UseAssetAssembly<AppT>(this AppT app, Assembly assembly) where AppT : Application
{
// Asset loading searches our own assembly?
var loader = new AssetLoader(assembly);
PerspexLocator.CurrentMutable.Bind<IAssetLoader>().ToConstant(loader);
return app;
}
}
}
namespace Perspex.iOS
{
// TODO: Perhaps we should make this class handle all these interfaces directly, like we
// do for Win32 and Gtk platforms
//
public class iOSPlatform //: IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform
{
internal static MouseDevice MouseDevice;
internal static KeyboardDevice KeyboardDevice;
public static void Initialize()
{
MouseDevice = new MouseDevice();
KeyboardDevice = new KeyboardDevice();
PerspexLocator.CurrentMutable
.Bind<IPclPlatformWrapper>().ToSingleton<PclPlatformWrapper>()
.Bind<IClipboard>().ToTransient<Clipboard>()
// TODO: what does this look like for iOS??
//.Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
}
}
}

20
src/iOS/Perspex.iOSTestApplication/AppDelegate.cs

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Foundation;
using Perspex.Controls;
using Perspex.iOS;
using Perspex.Media;
using Perspex.Threading;
using TestApplication;
using UIKit;
@ -14,7 +16,7 @@ namespace Perspex.iOSTestApplication
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : PerspexAppDelegate
public partial class AppDelegate : UIApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
@ -25,16 +27,18 @@ namespace Perspex.iOSTestApplication
//
public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options)
{
InitPerspex (typeof (App));
var app = new App()
.UseiOS()
.UseSkiaViewHost()
.UseSkia();
var app = new App();
MainWindow.RootNamespace = "Perspex.iOSTestApplication";
var window = MainWindow.Create();
window.Show();
app.Run(window);
var asm = typeof(App).Assembly;
app.UseAssetAssembly(asm);
app.Run();
return true;
}
}
}

68
src/iOS/Perspex.iOSTestApplication/Info.plist

@ -1,34 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Perspex.iOSTestApplication</string>
<key>CFBundleIdentifier</key>
<string>com.your-company.Perspex.iOSTestApplication</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>7.0</string>
</dict>
</plist>
<dict>
<key>CFBundleDisplayName</key>
<string>Perspex.iOSTestApplication</string>
<key>CFBundleIdentifier</key>
<string>com.your-company.Perspex.iOSTestApplication</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleIconFiles</key>
<array>
<string>Default-568h@2x.png</string>
</array>
</dict>
</plist>

8
src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj

@ -20,7 +20,7 @@
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>i386</MtouchArch>
<MtouchLink>None</MtouchLink>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchDebug>true</MtouchDebug>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
@ -47,7 +47,7 @@
<CodesignKey>iPhone Developer</CodesignKey>
<MtouchDebug>True</MtouchDebug>
<MtouchSdkVersion>9.1</MtouchSdkVersion>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchLink>None</MtouchLink>
<MtouchProfiling>False</MtouchProfiling>
<MtouchFastDev>False</MtouchFastDev>
<MtouchUseLlvm>False</MtouchUseLlvm>
@ -147,6 +147,10 @@
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\Perspex.HtmlRenderer\Perspex.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Perspex.HtmlRenderer</Name>

Loading…
Cancel
Save