Browse Source

Merge pull request #255 from PixiEditor/quickfill

Development branch test
Krzysztof Krysiński 3 years ago
parent
commit
3cfd15465c
100 changed files with 4801 additions and 2353 deletions
  1. 5 1
      Custom.ruleset
  2. 14 14
      Installer/installer-setup-x64-light.iss
  3. 14 14
      Installer/installer-setup-x86-light.iss
  4. 26 0
      PixiEditor.UpdateModule/UpdateChannel.cs
  5. 8 8
      PixiEditor.UpdateModule/UpdateChecker.cs
  6. 38 2
      PixiEditor.sln
  7. 0 15
      PixiEditor/Helpers/AssemblyHelper.cs
  8. 5 1
      PixiEditor/Helpers/Behaviours/MouseBehavior.cs
  9. 27 0
      PixiEditor/Helpers/Converters/DockingManagerActiveContentConverter.cs
  10. 1 1
      PixiEditor/Helpers/Converters/FinalIsVisibleToVisiblityConverter.cs
  11. 8 7
      PixiEditor/Helpers/Converters/IndexOfConverter.cs
  12. 25 6
      PixiEditor/Helpers/Converters/LayersToStructuredLayersConverter.cs
  13. 23 0
      PixiEditor/Helpers/Converters/SKColorToMediaColorConverter.cs
  14. 23 0
      PixiEditor/Helpers/Converters/ViewboxInverseTransformConverter.cs
  15. 168 0
      PixiEditor/Helpers/EllipseGenerator.cs
  16. 54 0
      PixiEditor/Helpers/Extensions/Int32RectEx.cs
  17. 104 91
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  18. 73 0
      PixiEditor/Helpers/Extensions/PixelFormatHelper.cs
  19. 11 0
      PixiEditor/Helpers/Extensions/PixiParserHelper.cs
  20. 63 0
      PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs
  21. 48 0
      PixiEditor/Helpers/VersionHelpers.cs
  22. 10 10
      PixiEditor/Models/Colors/ExColor.cs
  23. 145 146
      PixiEditor/Models/Controllers/BitmapManager.cs
  24. 96 158
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  25. 227 47
      PixiEditor/Models/Controllers/ClipboardController.cs
  26. 168 0
      PixiEditor/Models/Controllers/LayerStackRenderer.cs
  27. 14 3
      PixiEditor/Models/Controllers/MouseMovementController.cs
  28. 0 105
      PixiEditor/Models/Controllers/PixelChangesController.cs
  29. 101 0
      PixiEditor/Models/Controllers/SingleLayerRenderer.cs
  30. 39 0
      PixiEditor/Models/Controllers/SurfaceRenderer.cs
  31. 11 1
      PixiEditor/Models/Controllers/UndoManager.cs
  32. 15 19
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  33. 11 2
      PixiEditor/Models/DataHolders/Document/Document.Constructors.cs
  34. 2 1
      PixiEditor/Models/DataHolders/Document/Document.Discord.cs
  35. 70 40
      PixiEditor/Models/DataHolders/Document/Document.Layers.cs
  36. 138 3
      PixiEditor/Models/DataHolders/Document/Document.Operations.cs
  37. 12 2
      PixiEditor/Models/DataHolders/Document/Document.Preview.cs
  38. 31 32
      PixiEditor/Models/DataHolders/Document/Document.cs
  39. 0 24
      PixiEditor/Models/DataHolders/LayerChange.cs
  40. 15 24
      PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs
  41. 55 14
      PixiEditor/Models/DataHolders/Selection.cs
  42. 279 0
      PixiEditor/Models/DataHolders/Surface.cs
  43. 8 0
      PixiEditor/Models/Enums/FlipType.cs
  44. 32 7
      PixiEditor/Models/IO/Exporter.cs
  45. 53 11
      PixiEditor/Models/IO/Importer.cs
  46. 52 102
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  47. 257 0
      PixiEditor/Models/ImageManipulation/ToolCalculator.cs
  48. 220 116
      PixiEditor/Models/Layers/Layer.cs
  49. 6 7
      PixiEditor/Models/Layers/LayerHelper.cs
  50. 30 5
      PixiEditor/Models/Layers/Utils/LayerStructureUtils.cs
  51. 55 7
      PixiEditor/Models/Position/Coordinates.cs
  52. 40 34
      PixiEditor/Models/Position/CoordinatesCalculator.cs
  53. 82 0
      PixiEditor/Models/Position/CropData.cs
  54. 14 14
      PixiEditor/Models/Position/DoubleCoords.cs
  55. 62 0
      PixiEditor/Models/Services/DocumentProvider.cs
  56. 22 8
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  57. 13 0
      PixiEditor/Models/Tools/Brushes/Brush.cs
  58. 22 0
      PixiEditor/Models/Tools/Brushes/CircleBrush.cs
  59. 14 0
      PixiEditor/Models/Tools/Brushes/CircleBrushOverrides/InterestingShapeBrush.cs
  60. 25 0
      PixiEditor/Models/Tools/FloodFillRange.cs
  61. 94 0
      PixiEditor/Models/Tools/MatrixBrush.cs
  62. 15 14
      PixiEditor/Models/Tools/ShapeTool.cs
  63. 50 37
      PixiEditor/Models/Tools/Tool.cs
  64. 0 71
      PixiEditor/Models/Tools/ToolBuilder.cs
  65. 55 40
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  66. 77 161
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  67. 133 21
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  68. 35 36
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  69. 0 80
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  70. 133 0
      PixiEditor/Models/Tools/Tools/FloodFillTool.cs
  71. 152 92
      PixiEditor/Models/Tools/Tools/LineTool.cs
  72. 21 12
      PixiEditor/Models/Tools/Tools/MagicWandTool.cs
  73. 197 180
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  74. 199 150
      PixiEditor/Models/Tools/Tools/PenTool.cs
  75. 45 92
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  76. 17 8
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  77. 5 4
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  78. 13 1
      PixiEditor/Models/Undo/Change.cs
  79. 57 15
      PixiEditor/Models/Undo/StorageBasedChange.cs
  80. 37 5
      PixiEditor/PixiEditor.csproj
  81. 2 2
      PixiEditor/Properties/AssemblyInfo.cs
  82. 1 1
      PixiEditor/ViewModels/SettingsWindowViewModel.cs
  83. 2 7
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  84. 12 12
      PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs
  85. 2 7
      PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs
  86. 25 0
      PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs
  87. 10 10
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  88. 22 14
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  89. 24 31
      PixiEditor/ViewModels/SubViewModels/Main/StylusViewModel.cs
  90. 48 25
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  91. 5 29
      PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs
  92. 19 5
      PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  93. 2 2
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/DiscordSettings.cs
  94. 22 1
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs
  95. 1 4
      PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs
  96. 23 55
      PixiEditor/ViewModels/ViewModelMain.cs
  97. 3 1
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  98. 1 1
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs
  99. 6 0
      PixiEditor/Views/Dialogs/SettingsWindow.xaml
  100. 52 25
      PixiEditor/Views/MainWindow.xaml

+ 5 - 1
Custom.ruleset

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<RuleSet Name="Name" Description="Description" ToolsVersion="16.0">
+<RuleSet Name="Name" Description="Description" ToolsVersion="17.0">
   <Rules AnalyzerId="Microsoft.CodeAnalysis.CSharp" RuleNamespace="Microsoft.CodeAnalysis.CSharp">
     <Rule Id="AD0001" Action="None" />
   </Rules>
@@ -16,8 +16,10 @@
   <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
     <Rule Id="SA0001" Action="None" />
     <Rule Id="SA1000" Action="None" />
+    <Rule Id="SA1005" Action="None" />
     <Rule Id="SA1008" Action="None" />
     <Rule Id="SA1009" Action="None" />
+    <Rule Id="SA1028" Action="None" />
     <Rule Id="SA1101" Action="None" />
     <Rule Id="SA1110" Action="None" />
     <Rule Id="SA1111" Action="None" />
@@ -34,6 +36,8 @@
     <Rule Id="SA1139" Action="None" />
     <Rule Id="SA1200" Action="None" />
     <Rule Id="SA1201" Action="None" />
+    <Rule Id="SA1202" Action="None" />
+    <Rule Id="SA1204" Action="None" />
     <Rule Id="SA1207" Action="None" />
     <Rule Id="SA1208" Action="None" />
     <Rule Id="SA1209" Action="None" />

+ 14 - 14
Installer/installer-setup-x64-light.iss

@@ -4,8 +4,8 @@
 // requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
 #define UseNetCoreCheck
 #ifdef UseNetCoreCheck
-  ;#define UseDotNet50
-  #define UseDotNet50Desktop
+  ;#define UseDotNet60
+  #define UseDotNet60Desktop
 #endif
 
 // custom setup info
@@ -396,24 +396,24 @@ var
   Version: String;
 begin
 
-#ifdef UseDotNet50
-  // https://dotnet.microsoft.com/download/dotnet/5.0
-  if not IsNetCoreInstalled('Microsoft.NETCore.App 5.0.0') then begin
-    AddDependency('dotnet50' + GetArchitectureSuffix + '.exe',
+#ifdef UseDotNet60
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not IsNetCoreInstalled('Microsoft.NETCore.App 6.0.0') then begin
+    AddDependency('dotnet60' + GetArchitectureSuffix + '.exe',
       '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
-      '.NET Runtime 5.0' + GetArchitectureTitle,
-      GetString('https://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe'),
+      '.NET Runtime 6.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/34df41d5-c813-4e30-8aa3-3603ce6600c0/976e801af82c7108abbcb736a8bc5c14/dotnet-runtime-6.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/b9cfdb9e-d5cd-4024-b318-00390b729d2f/65690f2440f40654898020cdfffa1050/dotnet-runtime-6.0.0-win-x64.exe'),
       '', False, False, False);
   end;
 #endif
 
-#ifdef UseDotNet50Desktop
-  // https://dotnet.microsoft.com/download/dotnet/5.0
-  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.0') then begin
-    AddDependency('dotnet50desktop' + GetArchitectureSuffix + '.exe',
+#ifdef UseDotNet60Desktop
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 6.0.0') then begin
+    AddDependency('dotnet60desktop' + GetArchitectureSuffix + '.exe',
       '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
-      '.NET Desktop Runtime 5.0' + GetArchitectureTitle,
-      GetString('https://download.visualstudio.microsoft.com/download/pr/b2780d75-e54a-448a-95fc-da9721b2b4c2/62310a9e9f0ba7b18741944cbae9f592/windowsdesktop-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1b3a8899-127a-4465-a3c2-7ce5e4feb07b/1e153ad470768baa40ed3f57e6e7a9d8/windowsdesktop-runtime-5.0.0-win-x64.exe'),
+      '.NET Desktop Runtime 6.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/a1ca7d0d-ce01-4878-b952-3fa1e6d9a7c6/e386db367490b631b8c013a9fb0f3794/windowsdesktop-runtime-6.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/a865ccae-2219-4184-bcd6-0178dc580589/ba452d37e8396b7a49a9adc0e1a07e87/windowsdesktop-runtime-6.0.0-win-x64.exe'),
       '', False, False, False);
   end;
 #endif

+ 14 - 14
Installer/installer-setup-x86-light.iss

@@ -4,8 +4,8 @@
 // requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
 #define UseNetCoreCheck
 #ifdef UseNetCoreCheck
-  ;#define UseDotNet50
-  #define UseDotNet50Desktop
+  ;#define UseDotNet60
+  #define UseDotNet60Desktop
 #endif
 
 // custom setup info
@@ -395,24 +395,24 @@ var
   Version: String;
 begin
 
-#ifdef UseDotNet50
-  // https://dotnet.microsoft.com/download/dotnet/5.0
-  if not IsNetCoreInstalled('Microsoft.NETCore.App 5.0.0') then begin
-    AddDependency('dotnet50' + GetArchitectureSuffix + '.exe',
+#ifdef UseDotNet60
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not IsNetCoreInstalled('Microsoft.NETCore.App 6.0.0') then begin
+    AddDependency('dotnet60' + GetArchitectureSuffix + '.exe',
       '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
-      '.NET Runtime 5.0' + GetArchitectureTitle,
-      GetString('https://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe'),
+      '.NET Runtime 6.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/34df41d5-c813-4e30-8aa3-3603ce6600c0/976e801af82c7108abbcb736a8bc5c14/dotnet-runtime-6.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/b9cfdb9e-d5cd-4024-b318-00390b729d2f/65690f2440f40654898020cdfffa1050/dotnet-runtime-6.0.0-win-x64.exe'),
       '', False, False, False);
   end;
 #endif
 
-#ifdef UseDotNet50Desktop
-  // https://dotnet.microsoft.com/download/dotnet/5.0
-  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.0') then begin
-    AddDependency('dotnet50desktop' + GetArchitectureSuffix + '.exe',
+#ifdef UseDotNet60Desktop
+  // https://dotnet.microsoft.com/download/dotnet/6.0
+  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 6.0.0') then begin
+    AddDependency('dotnet60desktop' + GetArchitectureSuffix + '.exe',
       '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
-      '.NET Desktop Runtime 5.0' + GetArchitectureTitle,
-      GetString('https://download.visualstudio.microsoft.com/download/pr/b2780d75-e54a-448a-95fc-da9721b2b4c2/62310a9e9f0ba7b18741944cbae9f592/windowsdesktop-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1b3a8899-127a-4465-a3c2-7ce5e4feb07b/1e153ad470768baa40ed3f57e6e7a9d8/windowsdesktop-runtime-5.0.0-win-x64.exe'),
+      '.NET Desktop Runtime 6.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/a1ca7d0d-ce01-4878-b952-3fa1e6d9a7c6/e386db367490b631b8c013a9fb0f3794/windowsdesktop-runtime-6.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/a865ccae-2219-4184-bcd6-0178dc580589/ba452d37e8396b7a49a9adc0e1a07e87/windowsdesktop-runtime-6.0.0-win-x64.exe'),
       '', False, False, False);
   end;
 #endif

+ 26 - 0
PixiEditor.UpdateModule/UpdateChannel.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.UpdateModule
+{
+    public class UpdateChannel
+    {
+        public string Name { get; }
+        public string RepositoryOwner { get; }
+        public string RepositoryName { get; }
+        public string ApiUrl { get; } 
+        public string IncompatibleFileApiUrl { get; }
+
+        public UpdateChannel(string name, string repositoryOwner, string repositoryName)
+        {
+            Name = name;
+            RepositoryOwner = repositoryOwner;
+            RepositoryName = repositoryName;
+            ApiUrl = $"https://api.github.com/repos/{repositoryOwner}/{repositoryName}/releases/latest";
+            IncompatibleFileApiUrl = "https://raw.githubusercontent.com/" + $"{repositoryOwner}/{repositoryName}/" + "{0}/incompatible.json";
+        }
+    }
+}

+ 8 - 8
PixiEditor.UpdateModule/UpdateChecker.cs

@@ -10,16 +10,16 @@ namespace PixiEditor.UpdateModule
 {
     public class UpdateChecker
     {
-        private const string ReleaseApiUrl = "https://api.github.com/repos/PixiEditor/PixiEditor/releases/latest";
-        private const string IncompatibleFileApiUrl = "https://raw.githubusercontent.com/PixiEditor/PixiEditor/{0}/incompatible.json";
-
-        public UpdateChecker(string currentVersionTag)
+        public UpdateChecker(string currentVersionTag, UpdateChannel channel)
         {
             CurrentVersionTag = currentVersionTag;
+            Channel = channel;
         }
 
         public ReleaseInfo LatestReleaseInfo { get; private set; }
 
+        public UpdateChannel Channel { get; set; }
+
         public string CurrentVersionTag { get; }
 
         /// <summary>
@@ -45,7 +45,7 @@ namespace PixiEditor.UpdateModule
 
         public async Task<bool> CheckUpdateAvailable()
         {
-            LatestReleaseInfo = await GetLatestReleaseInfoAsync();
+            LatestReleaseInfo = await GetLatestReleaseInfoAsync(Channel.ApiUrl);
             return CheckUpdateAvailable(LatestReleaseInfo);
         }
 
@@ -70,7 +70,7 @@ namespace PixiEditor.UpdateModule
             using (HttpClient client = new HttpClient())
             {
                 client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
-                HttpResponseMessage response = await client.GetAsync(string.Format(IncompatibleFileApiUrl, tag));
+                HttpResponseMessage response = await client.GetAsync(string.Format(Channel.IncompatibleFileApiUrl, tag));
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     string content = await response.Content.ReadAsStringAsync();
@@ -81,12 +81,12 @@ namespace PixiEditor.UpdateModule
             return Array.Empty<string>();
         }
 
-        private static async Task<ReleaseInfo> GetLatestReleaseInfoAsync()
+        private static async Task<ReleaseInfo> GetLatestReleaseInfoAsync(string apiUrl)
         {
             using (HttpClient client = new HttpClient())
             {
                 client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
-                HttpResponseMessage response = await client.GetAsync(ReleaseApiUrl);
+                HttpResponseMessage response = await client.GetAsync(apiUrl);
                 if (response.StatusCode == HttpStatusCode.OK)
                 {
                     string content = await response.Content.ReadAsStringAsync();

+ 38 - 2
PixiEditor.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28729.10
+# Visual Studio Version 17
+VisualStudioVersion = 17.1.31911.260
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor", "PixiEditor\PixiEditor.csproj", "{2CCDDE79-06CB-4771-AF85-7B25313EBA30}"
 EndProject
@@ -25,6 +25,9 @@ Global
 		Debug|Any CPU = Debug|Any CPU
 		Debug|x64 = Debug|x64
 		Debug|x86 = Debug|x86
+		Dev Release|Any CPU = Dev Release|Any CPU
+		Dev Release|x64 = Dev Release|x64
+		Dev Release|x86 = Dev Release|x86
 		MSIX Debug|Any CPU = MSIX Debug|Any CPU
 		MSIX Debug|x64 = MSIX Debug|x64
 		MSIX Debug|x86 = MSIX Debug|x86
@@ -42,6 +45,12 @@ Global
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x64.Build.0 = Debug|x64
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x86.ActiveCfg = Debug|x86
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Debug|x86.Build.0 = Debug|x86
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|Any CPU.ActiveCfg = Dev Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|Any CPU.Build.0 = Dev Release|Any CPU
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|x64.ActiveCfg = Dev Release|x64
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|x64.Build.0 = Dev Release|x64
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|x86.ActiveCfg = Dev Release|x86
+		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.Dev Release|x86.Build.0 = Dev Release|x86
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|Any CPU.ActiveCfg = MSIX Debug|Any CPU
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|Any CPU.Build.0 = MSIX Debug|Any CPU
 		{2CCDDE79-06CB-4771-AF85-7B25313EBA30}.MSIX Debug|x64.ActiveCfg = MSIX Debug|x64
@@ -66,6 +75,12 @@ Global
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x64.Build.0 = Debug|x64
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x86.ActiveCfg = Debug|x86
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Debug|x86.Build.0 = Debug|x86
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|Any CPU.ActiveCfg = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|Any CPU.Build.0 = Release|Any CPU
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|x64.ActiveCfg = Release|x64
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|x64.Build.0 = Release|x64
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|x86.ActiveCfg = Release|x86
+		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.Dev Release|x86.Build.0 = Release|x86
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
 		{41B40602-2E8C-4B76-9BDB-B9FDE686ACCE}.MSIX Debug|x64.ActiveCfg = Debug|x64
@@ -90,6 +105,12 @@ Global
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x64.Build.0 = Debug|x64
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x86.ActiveCfg = Debug|x86
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Debug|x86.Build.0 = Debug|x86
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|Any CPU.ActiveCfg = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|Any CPU.Build.0 = Release|Any CPU
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|x64.ActiveCfg = Release|x64
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|x64.Build.0 = Release|x64
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|x86.ActiveCfg = Release|x86
+		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.Dev Release|x86.Build.0 = Release|x86
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
 		{80BB2920-3DC0-406C-9E2B-30B08D5CC7A8}.MSIX Debug|x64.ActiveCfg = Debug|x64
@@ -114,6 +135,12 @@ Global
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x64.Build.0 = Debug|x64
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x86.ActiveCfg = Debug|x86
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.Debug|x86.Build.0 = Debug|x86
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|Any CPU.Build.0 = Release|Any CPU
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|x64.ActiveCfg = Release|x64
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|x64.Build.0 = Release|x64
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|x86.ActiveCfg = Release|x86
+		{5193C1C1-8362-40FD-802B-E097E8C88082}.Dev Release|x86.Build.0 = Release|x86
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
 		{5193C1C1-8362-40FD-802B-E097E8C88082}.MSIX Debug|x64.ActiveCfg = Debug|x64
@@ -141,6 +168,15 @@ Global
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.ActiveCfg = Debug|x86
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.Build.0 = Debug|x86
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Debug|x86.Deploy.0 = Debug|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|Any CPU.Build.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|Any CPU.Deploy.0 = Release|Any CPU
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x64.ActiveCfg = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x64.Build.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x64.Deploy.0 = Release|x64
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x86.ActiveCfg = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x86.Build.0 = Release|x86
+		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.Dev Release|x86.Deploy.0 = Release|x86
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1F97F972-F9E8-4F35-A8B5-3F71408D2230}.MSIX Debug|Any CPU.Deploy.0 = Debug|Any CPU

+ 0 - 15
PixiEditor/Helpers/AssemblyHelper.cs

@@ -1,15 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-using System.Text;
-
-namespace PixiEditor.Helpers
-{
-    public static class AssemblyHelper
-    {
-        public static Version GetCurrentAssemblyVersion() => Assembly.GetExecutingAssembly().GetName().Version;
-
-        public static string GetCurrentAssemblyVersion(Func<Version, string> toString) => toString(GetCurrentAssemblyVersion());
-    }
-}

+ 5 - 1
PixiEditor/Helpers/Behaviours/MouseBehavior.cs

@@ -60,4 +60,8 @@ namespace PixiEditor.Helpers.Behaviours
             MouseY = pos.Y;
         }
     }
-}
+}
+
+#if PUBLISH
+#error Hi
+#endif

+ 27 - 0
PixiEditor/Helpers/Converters/DockingManagerActiveContentConverter.cs

@@ -0,0 +1,27 @@
+using PixiEditor.Models.DataHolders;
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class DockingManagerActiveContentConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value == null)
+                return DependencyProperty.UnsetValue;
+            if (value is Document document)
+                return document;
+            return DependencyProperty.UnsetValue;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is Document document)
+                return document;
+            return DependencyProperty.UnsetValue;
+        }
+    }
+}

+ 1 - 1
PixiEditor/Helpers/Converters/FinalIsVisibleToVisiblityConverter.cs

@@ -24,4 +24,4 @@ namespace PixiEditor.Helpers.Converters
                     : (object)Visibility.Collapsed;
         }
     }
-}
+}

+ 8 - 7
PixiEditor/Helpers/Converters/IndexOfConverter.cs

@@ -1,5 +1,4 @@
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels;
 using System;
 using System.Globalization;
@@ -12,11 +11,13 @@ namespace PixiEditor.Helpers.Converters
     {
         public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
-            BitmapManager bitmapManager = ViewModelMain.Current.BitmapManager;
+            if (value is Layer layer && ViewModelMain.Current.BitmapManager.ActiveDocument != null)
+            {
+                int index = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(layer);
+                return index;
+            }
 
-            return value is Layer layer && bitmapManager.ActiveDocument != null
-                   ? bitmapManager.ActiveDocument.Layers.IndexOf(layer)
-                   : Binding.DoNothing;
+            return Binding.DoNothing;
         }
     }
-}
+}

+ 25 - 6
PixiEditor/Helpers/Converters/LayersToStructuredLayersConverter.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Globalization;
 using System.Linq;
+using System.Windows;
 using System.Windows.Data;
 
 namespace PixiEditor.Helpers.Converters
@@ -13,7 +14,8 @@ namespace PixiEditor.Helpers.Converters
         : MultiValueMarkupConverter
     {
         private static StructuredLayerTree cachedTree;
-        private List<Guid> lastLayers = new List<Guid>();
+        private List<Guid> lastLayerGuids = new List<Guid>();
+        private IList<Layer> lastLayers = new List<Layer>();
         private ObservableCollection<GuidStructureItem> lastStructure = new ObservableCollection<GuidStructureItem>();
 
         public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
@@ -25,18 +27,21 @@ namespace PixiEditor.Helpers.Converters
                     cachedTree = new StructuredLayerTree(layers, structure);
                 }
 
-                if (TryFindStructureDifferences(structure) || lastLayers.Count != layers.Count || LayerOrderIsDifferent(layers))
+                if (TryFindStructureDifferences(structure) ||
+                    lastLayerGuids.Count != layers.Count ||
+                    LayerOrderIsDifferent(layers) ||
+                    LayersAreDifferentObjects(layers, lastLayers))
                 {
                     cachedTree = new StructuredLayerTree(layers, structure);
-
-                    lastLayers = layers.Select(x => x.LayerGuid).ToList();
+                    lastLayers = layers;
+                    lastLayerGuids = layers.Select(x => x.LayerGuid).ToList();
                     lastStructure = structure.CloneGroups();
                 }
 
                 return cachedTree.RootDirectoryItems;
             }
 
-            return new StructuredLayerTree(null, null);
+            return DependencyProperty.UnsetValue;
         }
 
         public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
@@ -47,7 +52,21 @@ namespace PixiEditor.Helpers.Converters
         private bool LayerOrderIsDifferent(IList<Layer> layers)
         {
             var guids = layers.Select(x => x.LayerGuid).ToArray();
-            return !guids.SequenceEqual(lastLayers);
+            return !guids.SequenceEqual(lastLayerGuids);
+        }
+
+        /// <summary>
+        /// This should trigger if you open and close the same files twice.
+        /// Even though the layers are technically the same, having two different objects screws things up down the line.
+        /// </summary>
+        private bool LayersAreDifferentObjects(IList<Layer> layers, IList<Layer> lastLayers)
+        {
+            for (int i = 0; i < layers.Count; i++)
+            {
+                if (layers[i] != lastLayers[i])
+                    return true;
+            }
+            return false;
         }
 
         private bool TryFindStructureDifferences(LayerStructure structure)

+ 23 - 0
PixiEditor/Helpers/Converters/SKColorToMediaColorConverter.cs

@@ -0,0 +1,23 @@
+using SkiaSharp;
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class SKColorToMediaColorConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var skcolor = (SKColor)value;
+            return Color.FromArgb(skcolor.Alpha, skcolor.Red, skcolor.Green, skcolor.Blue);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var color = (Color)value;
+            return new SKColor(color.R, color.G, color.B, color.A);
+        }
+    }
+}

+ 23 - 0
PixiEditor/Helpers/Converters/ViewboxInverseTransformConverter.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class ViewboxInverseTransformConverter : MultiValueMarkupConverter
+    {
+        public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            var transform = ((ContainerVisual)VisualTreeHelper.GetChild((DependencyObject)values[0], 0)).Transform;
+            if (transform == null)
+                return DependencyProperty.UnsetValue;
+            return transform.Inverse;
+        }
+
+        public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 168 - 0
PixiEditor/Helpers/EllipseGenerator.cs

@@ -0,0 +1,168 @@
+using PixiEditor.Models.Position;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers
+{
+    internal static class EllipseGenerator
+    {
+        public static List<DoubleCoords> SplitEllipseIntoLines(List<Coordinates> ellipse)
+        {
+            List<DoubleCoords> lines = new();
+            var sorted = ellipse.OrderBy(
+                a => a,
+                Comparer<Coordinates>.Create((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X)
+                );
+
+            int minX = int.MaxValue;
+            int maxX = int.MinValue;
+            Coordinates? prev = null;
+            foreach (var point in sorted)
+            {
+                if (prev.HasValue && point.Y != prev.Value.Y)
+                {
+                    int prevY = prev.Value.Y;
+                    lines.Add(new DoubleCoords(new(minX, prevY), new(maxX, prevY)));
+                    minX = int.MaxValue;
+                    maxX = int.MinValue;
+                }
+                minX = Math.Min(point.X, minX);
+                maxX = Math.Max(point.X, maxX);
+                prev = point;
+            }
+            lines.Add(new DoubleCoords(new(minX, prev.Value.Y), new(maxX, prev.Value.Y)));
+            return lines;
+        }
+        public static List<Coordinates> GenerateEllipseFromRect(DoubleCoords rect, List<Coordinates> listToFill = null)
+        {
+            float radiusX = (rect.Coords2.X - rect.Coords1.X) / 2.0f;
+            float radiusY = (rect.Coords2.Y - rect.Coords1.Y) / 2.0f;
+            float centerX = (rect.Coords1.X + rect.Coords2.X + 1) / 2.0f;
+            float centerY = (rect.Coords1.Y + rect.Coords2.Y + 1) / 2.0f;
+            return GenerateMidpointEllipse(radiusX, radiusY, centerX, centerY, listToFill);
+        }
+
+        /// <summary>
+        /// Draws an ellipse using it's center and radii
+        ///
+        /// Here is a usage example:
+        /// Let's say you want an ellipse that's 3 pixels wide and 3 pixels tall located in the top right corner of the canvas
+        /// It's center is at (1.5; 1.5). That's in the middle of a pixel
+        /// The radii are both equal to 1. Notice that it's 1 and not 1.5, since we want the ellipse to land in the middle of the pixel, not outside of it.
+        /// See desmos (note the inverted y axis): https://www.desmos.com/calculator/tq9uqg0hcq
+        ///
+        /// Another example:
+        /// 4x4 ellipse in the top right corner of the canvas
+        /// Center is at (2; 2). It's a place where 4 pixels meet
+        /// Both radii are 1.5. Making them 2 would make the ellipse touch the edges of pixels, whereas we want it to stay in the middle
+        /// </summary>
+        public static List<Coordinates> GenerateMidpointEllipse(
+            double halfWidth,
+            double halfHeight,
+            double centerX,
+            double centerY,
+            List<Coordinates> listToFill = null)
+        {
+            listToFill ??= new List<Coordinates>();
+            if (halfWidth < 1 || halfHeight < 1)
+            {
+                AddFallbackRectangle(halfWidth, halfHeight, centerX, centerY, listToFill);
+                return listToFill;
+            }
+
+            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
+
+            // Make sure we are always at the center of a pixel
+            double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
+            double currentY = centerY + halfHeight;
+
+
+            double currentSlope;
+
+            // from PI/2 to PI/4
+            do
+            {
+                AddRegionPoints(listToFill, currentX, centerX, currentY, centerY);
+
+                // calculate next pixel coords
+                currentX++;
+
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
+                {
+                    currentY--;
+                }
+
+                // calculate how far we've advanced
+                double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
+                double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
+                currentSlope = -(derivativeX / derivativeY);
+            }
+            while (currentSlope > -1 && currentY - centerY > 0.5);
+
+            // from PI/4 to 0
+            while (currentY - centerY >= 0)
+            {
+                AddRegionPoints(listToFill, currentX, centerX, currentY, centerY);
+
+                currentY--;
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
+                {
+                    currentX++;
+                }
+            }
+
+            return listToFill;
+        }
+
+        private static void AddFallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> coordinates)
+        {
+            int left = (int)Math.Floor(centerX - halfWidth);
+            int top = (int)Math.Floor(centerY - halfHeight);
+            int right = (int)Math.Floor(centerX + halfWidth);
+            int bottom = (int)Math.Floor(centerY + halfHeight);
+
+            for (int x = left; x <= right; x++)
+            {
+                coordinates.Add(new Coordinates(x, top));
+                coordinates.Add(new Coordinates(x, bottom));
+            }
+
+            for (int y = top; y <= bottom; y++)
+            {
+                coordinates.Add(new Coordinates(left, y));
+                coordinates.Add(new Coordinates(right, y));
+            }
+        }
+
+        private static void AddRegionPoints(List<Coordinates> coordinates, double x, double xc, double y, double yc)
+        {
+            int xFloor = (int)Math.Floor(x);
+            int yFloor = (int)Math.Floor(y);
+            int xFloorInv = (int)Math.Floor(-x + 2 * xc);
+            int yFloorInv = (int)Math.Floor(-y + 2 * yc);
+
+            //top and bottom or left and right
+            if (xFloor == xFloorInv || yFloor == yFloorInv)
+            {
+                coordinates.Add(new Coordinates(xFloor, yFloor));
+                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
+            }
+            //part of the arc
+            else
+            {
+                coordinates.Add(new Coordinates(xFloor, yFloor));
+                coordinates.Add(new Coordinates(xFloorInv, yFloorInv));
+                coordinates.Add(new Coordinates(xFloorInv, yFloor));
+                coordinates.Add(new Coordinates(xFloor, yFloorInv));
+            }
+        }
+    }
+}

+ 54 - 0
PixiEditor/Helpers/Extensions/Int32RectEx.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    static class Int32RectEx
+    {
+        public static Int32Rect Intersect(this Int32Rect rect, Int32Rect other)
+        {
+            int rectX2 = rect.X + rect.Width;
+            int rectY2 = rect.Y + rect.Height;
+
+            int otherX2 = other.X + other.Width;
+            int otherY2 = other.Y + other.Height;
+
+            int maxX1 = Math.Max(rect.X, other.X);
+            int maxY1 = Math.Max(rect.Y, other.Y);
+
+            int minX2 = Math.Min(rectX2, otherX2);
+            int minY2 = Math.Min(rectY2, otherY2);
+
+            int width = minX2 - maxX1;
+            int height = minY2 - maxY1;
+
+            if (width <= 0 || height <= 0)
+                return Int32Rect.Empty;
+
+            return new Int32Rect(maxX1, maxY1, width, height);
+        }
+
+        public static Int32Rect Expand(this Int32Rect rect, Int32Rect other)
+        {
+            int rectX2 = rect.X + rect.Width;
+            int rectY2 = rect.Y + rect.Height;
+
+            int otherX2 = other.X + other.Width;
+            int otherY2 = other.Y + other.Height;
+
+            int minX1 = Math.Min(rect.X, other.X);
+            int minY1 = Math.Min(rect.Y, other.Y);
+
+            int maxX2 = Math.Max(rectX2, otherX2);
+            int maxY2 = Math.Max(rectY2, otherY2);
+
+            int width = maxX2 - minX1;
+            int height = maxY2 - minY1;
+
+            if (width <= 0 || height <= 0)
+                return Int32Rect.Empty;
+
+            return new Int32Rect(minX1, minY1, width, height);
+        }
+    }
+}

+ 104 - 91
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -1,14 +1,12 @@
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
-using PixiEditor.Parser;
-using System;
+using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using SDColor = System.Drawing.Color;
-
+
 namespace PixiEditor.Helpers.Extensions
 {
     public static class ParserHelpers
@@ -18,11 +16,10 @@ namespace PixiEditor.Helpers.Extensions
             Document document = new Document(serializableDocument.Width, serializableDocument.Height)
             {
                 Layers = serializableDocument.ToLayers(),
-                Swatches = new ObservableCollection<Color>(serializableDocument.Swatches.Select(x =>
-                    Color.FromArgb(x.A, x.R, x.G, x.B)))
+                Swatches = new ObservableCollection<SKColor>(serializableDocument.Swatches.ToSKColors())
             };
 
-            document.LayerStructure.Groups = serializableDocument.ToGroups();
+            document.LayerStructure.Groups = serializableDocument.ToGroups(document);
 
             if (document.Layers.Count > 0)
             {
@@ -32,113 +29,129 @@ namespace PixiEditor.Helpers.Extensions
             return document;
         }
 
-        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument serializableDocument)
-        {
-            return ToGroups(serializableDocument.Groups);
+        public static ObservableCollection<Layer> ToLayers(this SerializableDocument document)
+        {
+            ObservableCollection<Layer> layers = new();
+            foreach (SerializableLayer slayer in document)
+            {
+                layers.Add(slayer.ToLayer());
+            }
+
+            return layers;
         }
 
-        public static ObservableCollection<Layer> ToLayers(this SerializableDocument serializableDocument)
+        public static Layer ToLayer(this SerializableLayer layer)
         {
-            ObservableCollection<Layer> layers = new ObservableCollection<Layer>();
-            for (int i = 0; i < serializableDocument.Layers.Count; i++)
+            return new Layer(layer.Name, new Surface(layer.ToSKImage()))
             {
-                SerializableLayer serLayer = serializableDocument.Layers[i];
-                Layer layer =
-                    new Layer(serLayer.Name, BitmapUtils.BytesToWriteableBitmap(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
-                    {
-                        IsVisible = serLayer.IsVisible,
-                        Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),
-                        Opacity = serLayer.Opacity,
-                        MaxHeight = serializableDocument.Height,
-                        MaxWidth = serializableDocument.Width,
-                    };
-                if (serLayer.LayerGuid != Guid.Empty)
-                {
-                    layer.ChangeGuid(serLayer.LayerGuid);
-                }
-                layers.Add(layer);
+                Opacity = layer.Opacity,
+                IsVisible = layer.IsVisible,
+                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)
+            };
+        }
+
+        public static ObservableCollection<GuidStructureItem> ToGroups(this SerializableDocument sdocument, Document document)
+        {
+            ObservableCollection<GuidStructureItem> groups = new();
+
+            if (sdocument.Groups == null)
+            {
+                return groups;
             }
 
-            return layers;
+            foreach (SerializableGroup sgroup in sdocument.Groups)
+            {
+                groups.Add(sgroup.ToGroup(null, document));
+            }
+
+            return groups;
         }
 
-        public static SerializableDocument ToSerializable(this Document document)
+        public static GuidStructureItem ToGroup(this SerializableGroup sgroup, GuidStructureItem parent, Document document)
         {
-            SerializableDocument serializable = new SerializableDocument
+            GuidStructureItem group = new GuidStructureItem(sgroup.Name, Guid.Empty)
             {
-                Width = document.Width,
-                Height = document.Height,
-                Layers = document.Layers.Select(x => x.ToSerializable()).ToList(),
-                Groups = document.LayerStructure.Groups.Select(x => x.ToSerializable()).ToArray(),
-                Swatches = document.Swatches.Select(x => SDColor.FromArgb(x.A, x.R, x.G, x.B)).ToList()
+                Opacity = sgroup.Opacity,
+                IsVisible = sgroup.IsVisible,
+                Parent = parent,
+                StartLayerGuid = document.Layers[sgroup.StartLayer].LayerGuid,
+                EndLayerGuid = document.Layers[sgroup.EndLayer].LayerGuid
             };
 
-            return serializable;
+            group.Subgroups = new(sgroup.Subgroups.ToGroups(document, group));
+
+            return group;
+        }
+
+        public static SerializableDocument ToSerializable(this Document document)
+        {
+            return new SerializableDocument(document.Width, document.Height,
+                                            document.LayerStructure.Groups.ToSerializable(document),
+                                            document.Layers.ToSerializable()).AddSwatches(document.Swatches);
         }
 
-        public static SerializableGuidStructureItem ToSerializable(this GuidStructureItem group, SerializableGuidStructureItem parent = null)
+        public static IEnumerable<SerializableLayer> ToSerializable(this IEnumerable<Layer> layers)
         {
-            var serializedGroup = new SerializableGuidStructureItem(
-                    group.GroupGuid,
-                    group.Name,
-                    group.StartLayerGuid,
-                    group.EndLayerGuid,
-                    null, group.IsVisible, group.Opacity);
-            serializedGroup.Subgroups = group.Subgroups.Select(x => x.ToSerializable(serializedGroup)).ToArray();
-            return serializedGroup;
+            foreach (Layer layer in layers)
+            {
+                yield return layer.ToSerializable();
+            }
         }
 
         public static SerializableLayer ToSerializable(this Layer layer)
         {
-            SerializableLayer serializable = new SerializableLayer
+            return new SerializableLayer(layer.Width, layer.Height, layer.OffsetX, layer.OffsetY)
             {
-                LayerGuid = layer.LayerGuid,
-                Name = layer.Name,
-                Width = layer.Width,
-                Height = layer.Height,
-                BitmapBytes = layer.ConvertBitmapToBytes(),
                 IsVisible = layer.IsVisible,
-                OffsetX = (int)layer.Offset.Left,
-                OffsetY = (int)layer.Offset.Top,
                 Opacity = layer.Opacity,
-                MaxWidth = layer.MaxWidth,
-                MaxHeight = layer.MaxHeight
+                Name = layer.Name
+            }.FromSKImage(layer.LayerBitmap.SkiaSurface.Snapshot());
+        }
+
+        public static IEnumerable<SerializableGroup> ToSerializable(this IEnumerable<GuidStructureItem> groups, Document document)
+        {
+            foreach (GuidStructureItem group in groups)
+            {
+                yield return group.ToSerializable(document);
+            }
+        }
+
+        public static SerializableGroup ToSerializable(this GuidStructureItem group, Document document)
+        {
+            SerializableGroup serializable = new SerializableGroup(group.Name, group.Subgroups.ToSerializable(document))
+            {
+                Opacity = group.Opacity,
+                IsVisible = group.IsVisible
             };
 
+            for (int i = 0; i < document.Layers.Count; i++)
+            {
+                if (group.StartLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.StartLayer = i;
+                }
+
+                if (group.EndLayerGuid == document.Layers[i].LayerGuid)
+                {
+                    serializable.EndLayer = i;
+                }
+            }
+
             return serializable;
-        }
-
-        private static ObservableCollection<GuidStructureItem> ToGroups(SerializableGuidStructureItem[] serializableGroups, GuidStructureItem parent = null)
-        {
-            ObservableCollection<GuidStructureItem> groups = new ObservableCollection<GuidStructureItem>();
-
-            if (serializableGroups == null)
-            {
-                return groups;
-            }
-
-            foreach (var serializableGroup in serializableGroups)
-            {
-                groups.Add(ToGroup(serializableGroup, parent));
-            }
-            return groups;
         }
 
-        private static GuidStructureItem ToGroup(SerializableGuidStructureItem group, GuidStructureItem parent = null)
-        {
-            if (group == null)
-            {
-                return null;
-            }
-            var parsedGroup = new GuidStructureItem(
-                group.Name,
-                group.StartLayerGuid,
-                group.EndLayerGuid,
-                new ObservableCollection<GuidStructureItem>(),
-                parent)
-            { Opacity = group.Opacity, IsVisible = group.IsVisible, GroupGuid = group.GroupGuid, IsExpanded = true };
-            parsedGroup.Subgroups = ToGroups(group.Subgroups, parsedGroup);
-            return parsedGroup;
+        private static IEnumerable<GuidStructureItem> ToGroups(this IEnumerable<SerializableGroup> groups, Document document, GuidStructureItem parent)
+        {
+            foreach (SerializableGroup sgroup in groups)
+            {
+                yield return sgroup.ToGroup(parent, document);
+            }
+        }
+
+        private static SerializableDocument AddSwatches(this SerializableDocument document, IEnumerable<SKColor> colors)
+        {
+            document.Swatches.AddRange(colors);
+            return document;
         }
     }
 }

+ 73 - 0
PixiEditor/Helpers/Extensions/PixelFormatHelper.cs

@@ -0,0 +1,73 @@
+using SkiaSharp;
+using System;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class PixelFormatHelper
+    {
+        public static SKColorType ToSkia(this PixelFormat format, out SKAlphaType alphaType)
+        {
+            if (TryToSkia(format, out SKColorType color, out alphaType))
+            {
+                return color;
+            }
+            else
+            {
+                throw new NotImplementedException($"Skia does not support the '{format}' format");
+            }
+        }
+
+        public static bool TryToSkia(this PixelFormat format, out SKColorType colorType, out SKAlphaType alphaType)
+        {
+            if (format == PixelFormats.Rgba64)
+            {
+                alphaType = SKAlphaType.Unpremul;
+                colorType = SKColorType.Rgba16161616;
+                return true;
+            }
+            else if (format == PixelFormats.Bgra32)
+            {
+                alphaType = SKAlphaType.Unpremul;
+                colorType = SKColorType.Bgra8888;
+                return true;
+            }
+            else if (format == PixelFormats.Default)
+            {
+                alphaType = SKAlphaType.Unpremul;
+                colorType = SKColorType.RgbaF16;
+                return true;
+            }
+            else if (format == PixelFormats.Gray8)
+            {
+                alphaType = SKAlphaType.Opaque;
+                colorType = SKColorType.Gray8;
+                return true;
+            }
+            else if (format == PixelFormats.Pbgra32)
+            {
+                alphaType = SKAlphaType.Premul;
+                colorType = SKColorType.Bgra8888;
+                return true;
+            }
+            else if (format == PixelFormats.Bgr101010 || format == PixelFormats.Bgr24 || format == PixelFormats.Bgr32 || format == PixelFormats.Bgr555 ||
+                     format == PixelFormats.Bgr565 || format == PixelFormats.BlackWhite || format == PixelFormats.Cmyk32 || format == PixelFormats.Gray16 ||
+                     format == PixelFormats.Gray2 || format == PixelFormats.Gray32Float || format == PixelFormats.Gray4 || format == PixelFormats.Indexed1 ||
+                     format == PixelFormats.Indexed2 || format == PixelFormats.Indexed4 || format == PixelFormats.Indexed8 || format == PixelFormats.Prgba128Float ||
+                     format == PixelFormats.Prgba64 || format == PixelFormats.Rgb128Float || format == PixelFormats.Rgb24 || format == PixelFormats.Rgb48 ||
+                     format == PixelFormats.Rgba128Float)
+            {
+                alphaType = SKAlphaType.Unknown;
+                colorType = SKColorType.Unknown;
+                return false;
+            }
+
+            throw new NotImplementedException($"'{format}' has not been implemented by {nameof(PixelFormatHelper)}.{nameof(TryToSkia)}()");
+        }
+
+        public static bool IsSkiaSupported(this PixelFormat format)
+        {
+            return TryToSkia(format, out _, out _);
+        }
+    }
+}

+ 11 - 0
PixiEditor/Helpers/Extensions/PixiParserHelper.cs

@@ -0,0 +1,11 @@
+using PixiEditor.Parser;
+using SkiaSharp;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class PixiParserHelper
+    {
+        public static SKRectI GetRect(this SerializableLayer layer) =>
+            SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
+    }
+}

+ 63 - 0
PixiEditor/Helpers/Extensions/ServiceCollectionHelpers.cs

@@ -0,0 +1,63 @@
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Controllers.Shortcuts;
+using PixiEditor.Models.Services;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.Models.UserPreferences;
+using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.SubViewModels.Main;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ServiceCollectionHelpers
+    {
+        /// <summary>
+        /// Add's all the services required to fully run PixiEditor's MainWindow
+        /// </summary>
+        public static IServiceCollection AddPixiEditor(this IServiceCollection collection) => collection
+                .AddSingleton<ViewModelMain>()
+                .AddSingleton<IPreferences, PreferencesSettings>()
+                // View Models
+                .AddSingleton<StylusViewModel>()
+                .AddSingleton<WindowViewModel>()
+                .AddSingleton<ToolsViewModel>()
+                .AddSingleton<FileViewModel>()
+                .AddSingleton<UpdateViewModel>()
+                .AddSingleton<IoViewModel>()
+                .AddSingleton<LayersViewModel>()
+                .AddSingleton<ClipboardViewModel>()
+                .AddSingleton<UndoViewModel>()
+                .AddSingleton<SelectionViewModel>()
+                .AddSingleton<ViewportViewModel>()
+                .AddSingleton<ColorsViewModel>()
+                .AddSingleton<DocumentViewModel>()
+                .AddSingleton<MiscViewModel>()
+                .AddSingleton(x => new DiscordViewModel(x.GetService<ViewModelMain>(), "764168193685979138"))
+                .AddSingleton<DebugViewModel>()
+                // Controllers
+                .AddSingleton<ShortcutController>()
+                .AddSingleton<BitmapManager>()
+                // Tools
+                .AddSingleton<Tool, MoveViewportTool>()
+                .AddSingleton<Tool, MoveTool>()
+                .AddSingleton<Tool, PenTool>()
+                .AddSingleton<Tool, SelectTool>()
+                .AddSingleton<Tool, MagicWandTool>()
+                .AddSingleton<Tool, FloodFillTool>()
+                .AddSingleton<Tool, LineTool>()
+                .AddSingleton<Tool, CircleTool>()
+                .AddSingleton<Tool, RectangleTool>()
+                .AddSingleton<Tool, EraserTool>()
+                .AddSingleton<Tool, ColorPickerTool>()
+                .AddSingleton<Tool, BrightnessTool>()
+                .AddSingleton<Tool, ZoomTool>()
+                // Other
+                .AddSingleton<DocumentProvider>();
+    }
+}

+ 48 - 0
PixiEditor/Helpers/VersionHelpers.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+
+namespace PixiEditor.Helpers
+{
+    public static class VersionHelpers
+    {
+        public static Version GetCurrentAssemblyVersion() => Assembly.GetExecutingAssembly().GetName().Version;
+
+        public static string GetCurrentAssemblyVersion(Func<Version, string> toString) => toString(GetCurrentAssemblyVersion());
+
+        public static string GetCurrentAssemblyVersionString()
+        {
+            StringBuilder builder = new(GetCurrentAssemblyVersion().ToString());
+
+            bool isDone = false;
+
+            AppendDevBuild(builder, ref isDone);
+
+            if (isDone)
+            {
+                return builder.ToString();
+            }
+
+            AppendDebugBuild(builder, ref isDone);
+
+            return builder.ToString();
+        }
+
+        [Conditional("DEV_RELEASE")]
+        private static void AppendDevBuild(StringBuilder builder, ref bool done)
+        {
+            done = true;
+
+            builder.Append(" Dev Build");
+        }
+
+        [Conditional("DEBUG")]
+        private static void AppendDebugBuild(StringBuilder builder, ref bool done)
+        {
+            done = true;
+
+            builder.Append(" Debug Build");
+        }
+    }
+}

+ 10 - 10
PixiEditor/Models/Colors/ExColor.cs

@@ -1,5 +1,5 @@
-using System;
-using System.Windows.Media;
+using SkiaSharp;
+using System;
 
 namespace PixiEditor.Models.Colors
 {
@@ -14,19 +14,19 @@ namespace PixiEditor.Models.Colors
         ///     Negative values produce darker colors.
         /// </param>
         /// <returns>
-        ///     Corrected <see cref="Color" /> structure.
+        ///     Corrected <see cref="SKColor" /> structure.
         /// </returns>
-        public static Color ChangeColorBrightness(Color color, float correctionFactor)
+        public static SKColor ChangeColorBrightness(SKColor color, float correctionFactor)
         {
-            Tuple<int, float, float> hsl = RgbToHsl(color.R, color.G, color.B);
+            Tuple<int, float, float> hsl = RgbToHsl(color.Red, color.Green, color.Blue);
             int h = hsl.Item1;
             float s = hsl.Item2;
             float l = hsl.Item3;
 
             l = Math.Clamp(l + correctionFactor, 0, 100);
-            Color rgb = HslToRgb(h, s, l);
+            SKColor rgb = HslToRgb(h, s, l);
 
-            return Color.FromArgb(color.A, rgb.R, rgb.G, rgb.B);
+            return new SKColor(rgb.Red, rgb.Green, rgb.Blue, color.Alpha);
         }
 
         /// <summary>
@@ -94,7 +94,7 @@ namespace PixiEditor.Models.Colors
         ///     Converts HSL color format to RGB.
         /// </summary>
         /// <returns>RGB Color.</returns>
-        public static Color HslToRgb(int h, float s, float l)
+        public static SKColor HslToRgb(int h, float s, float l)
         {
             s /= 100;
             l /= 100;
@@ -119,7 +119,7 @@ namespace PixiEditor.Models.Colors
                 b = (byte)(255 * HueToRgb(v1, v2, hue - (1.0f / 3)));
             }
 
-            return Color.FromRgb(r, g, b);
+            return new SKColor(r, g, b);
         }
 
         private static float HueToRgb(float v1, float v2, float hue)
@@ -152,4 +152,4 @@ namespace PixiEditor.Models.Colors
             return v1;
         }
     }
-}
+}

+ 145 - 146
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,80 +1,66 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Events;
-using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.SubViewModels.Main;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.Models.Controllers
 {
     [DebuggerDisplay("{Documents.Count} Document(s)")]
     public class BitmapManager : NotifyableObject
     {
-        private Document activeDocument;
-        private Tool selectedTool;
+        private readonly ToolsViewModel _tools;
+
+        private int previewLayerSize;
+        private Document activeDocument;
         private Coordinates? startPosition = null;
+        private int halfSize;
+        private SKColor _highlightColor;
+        private PenTool _highlightPen;
+        private bool hideReferenceLayer;
+        private bool onlyReferenceLayer;
 
-        public BitmapManager()
+        public BitmapManager(ToolsViewModel tools)
         {
+            _tools = tools;
+
             MouseController = new MouseMovementController();
             MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
             MouseController.MousePositionChanged += Controller_MousePositionChanged;
             MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             MouseController.OnMouseDown += MouseController_OnMouseDown;
             MouseController.OnMouseUp += MouseController_OnMouseUp;
-            BitmapOperations = new BitmapOperationsUtility(this);
+            MouseController.OnMouseDownCoordinates += MouseController_OnMouseDownCoordinates;
+            BitmapOperations = new BitmapOperationsUtility(this, tools);
             ReadonlyToolUtility = new ReadonlyToolUtility();
-        }
+            DocumentChanged += BitmapManager_DocumentChanged;
+            _highlightPen = new PenTool(this)
+            {
+                AutomaticallyResizeCanvas = false
+            };
+            _highlightColor = new SKColor(0, 0, 0, 77);
+        }
 
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
 
-        public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
-
         public MouseMovementController MouseController { get; set; }
 
-        public Tool SelectedTool
-        {
-            get => selectedTool;
-            private set
-            {
-                Tool previousTool = selectedTool;
-                if (SetProperty(ref selectedTool, value))
-                {
-                    SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(previousTool, value));
-                }
-            }
-        }
-
         public Layer ActiveLayer => ActiveDocument.ActiveLayer;
 
-        public Color PrimaryColor { get; set; }
-
-        public int ToolSize
-        {
-            get => SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
-            ? SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
-            : 1;
-            set
-            {
-                if (SelectedTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is SizeSetting toolSize)
-                {
-                    toolSize.Value = value;
-                    HighlightPixels(MousePositionConverter.CurrentCoordinates);
-                }
-            }
-        }
+        public SKColor PrimaryColor { get; set; }
 
         public BitmapOperationsUtility BitmapOperations { get; set; }
 
@@ -96,13 +82,17 @@ namespace PixiEditor.Models.Controllers
 
 #nullable disable
         public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
+
+        public bool HideReferenceLayer
+        {
+            get => hideReferenceLayer;
+            set => SetProperty(ref hideReferenceLayer, value);
+        }
 
-        /// <summary>
-        ///     Returns if tool is BitmapOperationTool.
-        /// </summary>
-        public static bool IsOperationTool(Tool tool)
-        {
-            return tool is BitmapOperationTool;
+        public bool OnlyReferenceLayer
+        {
+            get => onlyReferenceLayer;
+            set => SetProperty(ref onlyReferenceLayer, value);
         }
 
         public void CloseDocument(Document document)
@@ -116,66 +106,94 @@ namespace PixiEditor.Models.Controllers
 
             Documents.Remove(document);
             ActiveDocument = nextIndex >= 0 ? Documents[nextIndex] : null;
+            document.Dispose();
         }
 
         public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
         {
-            if (SelectedTool.CanStartOutsideCanvas || clickedOnCanvas)
-            {
-                if (startPosition == null)
-                {
-                    SelectedTool.OnStart(newPosition);
-                    startPosition = newPosition;
-                }
+            Tool activeTool = _tools.ActiveTool;
 
-                if (SelectedTool is BitmapOperationTool operationTool)
-                {
-                    BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates, operationTool);
-                }
-                else if (SelectedTool is ReadonlyTool readonlyTool)
-                {
-                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates, readonlyTool);
-                }
-                else
-                {
-                    throw new InvalidOperationException($"'{SelectedTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
-                }
-            }
+            if (activeTool.CanStartOutsideCanvas && !clickedOnCanvas)
+            {
+                return;
+            }
+
+            if (startPosition == null)
+            {
+                activeTool.OnStart(newPosition);
+                startPosition = newPosition;
+            }
+
+            if (activeTool is BitmapOperationTool operationTool)
+            {
+                BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates, operationTool);
+            }
+            else if (activeTool is ReadonlyTool readonlyTool)
+            {
+                ActiveDocument.PreviewLayer.Reset();
+                ReadonlyToolUtility.ExecuteTool(
+                    MouseController.LastMouseMoveCoordinates,
+                    readonlyTool);
+            }
+            else
+            {
+                throw new InvalidOperationException($"'{activeTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
+            }
         }
 
-        public WriteableBitmap GetCombinedLayersBitmap()
-        {
-            return BitmapUtils.CombineLayers(ActiveDocument.Width, ActiveDocument.Height, ActiveDocument.Layers.Where(x => ActiveDocument.GetFinalLayerIsVisible(x)).ToArray(), ActiveDocument.LayerStructure);
-        }
-
-        /// <summary>
-        ///     Returns if selected tool is BitmapOperationTool.
-        /// </summary>
-        public bool IsOperationTool()
+        public void HighlightPixels(Coordinates newPosition)
         {
-            return IsOperationTool(SelectedTool);
-        }
+            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || _tools.ActiveTool.HideHighlight)
+            {
+                return;
+            }
 
-        public void SetActiveTool(Tool tool)
-        {
-            if (ActiveDocument != null)
+            var previewLayer = ActiveDocument.PreviewLayer;
+
+            if (_tools.ToolSize != previewLayerSize || previewLayer.IsReset)
             {
-                ActiveDocument.PreviewLayer = null;
+                previewLayerSize = _tools.ToolSize;
+                halfSize = (int)Math.Floor(_tools.ToolSize / 2f);
+                previewLayer.CreateNewBitmap(_tools.ToolSize, _tools.ToolSize);
+
+                Coordinates cords = new Coordinates(halfSize, halfSize);
+
+                previewLayer.Offset = new Thickness(0, 0, 0, 0);
+                _highlightPen.Draw(previewLayer, cords, cords, _highlightColor, _tools.ToolSize);
+
+                AdjustOffset(newPosition, previewLayer);
+
             }
 
-            SelectedTool?.Toolbar.SaveToolbarSettings();
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
+            previewLayer.InvokeLayerBitmapChange();
+
+            AdjustOffset(newPosition, previewLayer);
+
+            if (newPosition.X > ActiveDocument.Width
+                || newPosition.Y > ActiveDocument.Height
+                || newPosition.X < 0 || newPosition.Y < 0)
+            {
+                previewLayer.Reset();
+                previewLayerSize = -1;
+            }
+        }
+
+        private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
+        {
+            e.NewDocument?.GeneratePreviewLayer();
         }
 
         private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
         {
-            SelectedTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
-            {
-                ExecuteTool(e.NewPosition, MouseController.ClickedOnCanvas);
+            Tool activeTool = _tools.ActiveTool;
+
+            if (activeTool == null)
+            {
+                return;
             }
-            else if (Mouse.LeftButton == MouseButtonState.Released)
+
+            activeTool.OnMouseMove(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (!MaybeExecuteTool(e.NewPosition) && MouseController.LeftMouseState == MouseButtonState.Released)
             {
                 HighlightPixels(e.NewPosition);
             }
@@ -183,79 +201,60 @@ namespace PixiEditor.Models.Controllers
 
         private void MouseController_OnMouseDown(object sender, MouseEventArgs e)
         {
-            SelectedTool.OnMouseDown(e);
+            _tools.ActiveTool.OnMouseDown(e);
         }
 
         private void MouseController_OnMouseUp(object sender, MouseEventArgs e)
         {
-            SelectedTool.OnMouseUp(e);
+            _tools.ActiveTool.OnMouseUp(e);
+        }
+        private void MouseController_OnMouseDownCoordinates(object sender, MouseMovementEventArgs e)
+        {
+            MaybeExecuteTool(e.NewPosition);
+        }
+
+        private bool MaybeExecuteTool(Coordinates newPosition)
+        {
+            if (MouseController.LeftMouseState == MouseButtonState.Pressed && !IsDraggingViewport() && ActiveDocument != null)
+            {
+                ExecuteTool(newPosition, MouseController.ClickedOnCanvas);
+                return true;
+            }
+            return false;
         }
 
         private bool IsDraggingViewport()
         {
-            return SelectedTool is MoveViewportTool;
+            return _tools.ActiveTool is MoveViewportTool;
         }
 
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         {
-            SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            _tools.ActiveTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
             if (ActiveDocument != null)
             {
-                ActiveDocument.PreviewLayer = null;
+                ActiveDocument.PreviewLayer.Reset();
             }
         }
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
         {
-            SelectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool)SelectedTool).RequiresPreviewLayer)
+            Tool selectedTool = _tools.ActiveTool;
+            selectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
+            if (selectedTool is BitmapOperationTool operationTool && operationTool.RequiresPreviewLayer)
             {
                 BitmapOperations.ApplyPreviewLayer();
             }
 
+            HighlightPixels(MousePositionConverter.CurrentCoordinates);
+
             startPosition = null;
-        }
-
-        private void HighlightPixels(Coordinates newPosition)
-        {
-            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || SelectedTool.HideHighlight)
-            {
-                return;
-            }
+        }
 
-            IEnumerable<Coordinates> highlightArea = CoordinatesCalculator.RectangleToCoordinates(
-                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
-            if (CanChangeHighlightOffset(highlightArea))
-            {
-                Coordinates start = highlightArea.First();
-                ActiveDocument.PreviewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
-            }
-            else if (!IsInsideBounds(highlightArea))
-            {
-                ActiveDocument.PreviewLayer = null;
-            }
-            else
-            {
-                ActiveDocument.GeneratePreviewLayer();
-                ActiveDocument.PreviewLayer.SetPixels(
-                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
-            }
-        }
-
-        private bool CanChangeHighlightOffset(IEnumerable<Coordinates> highlightArea)
-        {
-            int count = highlightArea.Count();
-            return count > 0 && ActiveDocument.PreviewLayer != null &&
-                   IsInsideBounds(highlightArea) && count == ActiveDocument.PreviewLayer.Width * ActiveDocument.PreviewLayer.Height;
-        }
-
-        private bool IsInsideBounds(IEnumerable<Coordinates> highlightArea)
-        {
-            Coordinates start = highlightArea.First();
-            Coordinates end = highlightArea.Last();
-            return start.X <= ActiveDocument.Width - 1 &&
-                    start.Y <= ActiveDocument.Height - 1 &&
-                   end.X >= 0 && end.Y >= 0;
-        }
+        private void AdjustOffset(Coordinates newPosition, Layer previewLayer)
+        {
+            Coordinates start = newPosition - halfSize;
+            previewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
+        }
     }
-}
+}

+ 96 - 158
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -1,41 +1,37 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Undo;
-using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.SubViewModels.Main;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.Models.Controllers
 {
     public class BitmapOperationsUtility
     {
-        public List<LayerChange> PreviewLayerChanges => previewLayerChanges;
-
-        private List<LayerChange> previewLayerChanges;
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
 
-        private Coordinates lastMousePos;
 
         private SizeSetting sizeSetting;
 
-        public BitmapOperationsUtility(BitmapManager manager)
+        public BitmapOperationsUtility(BitmapManager manager, ToolsViewModel tools)
         {
             Manager = manager;
+            Tools = tools;
         }
 
         public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
 
         public BitmapManager Manager { get; set; }
 
+        public ToolsViewModel Tools { get; set; }
+
         public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
             if (Manager.ActiveDocument == null)
@@ -43,20 +39,25 @@ namespace PixiEditor.Models.Controllers
                 return;
             }
 
-            BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
-            Dictionary<Guid, Color[]> oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
-            LayerChange[] old = new LayerChange[layers.Length];
-            LayerChange[] newChange = new LayerChange[layers.Length];
+            StorageBasedChange change = new StorageBasedChange(Manager.ActiveDocument, layers, true);
+            
+
+            // TODO: Fix
+            BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, SKColors.Empty);
+            //Dictionary<Guid, SKColor[]> oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
+            //LayerChange[] old = new LayerChange[layers.Length];
+            //LayerChange[] newChange = new LayerChange[layers.Length];
             for (int i = 0; i < layers.Length; i++)
             {
                 Guid guid = layers[i].LayerGuid;
-                old[i] = new LayerChange(
-                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i].LayerGuid]), guid);
-                newChange[i] = new LayerChange(changes, guid);
+                //old[i] = new LayerChange(
+                    //BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i].LayerGuid]), guid);
+                //newChange[i] = new LayerChange(changes, guid);
                 layers[i].SetPixels(changes);
             }
 
-            Manager.ActiveDocument.UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
+            var args = new object[] { change.Document };
+            Manager.ActiveDocument.UndoManager.AddUndoChange(change.ToChange(StorageBasedChange.BasicUndoProcess, args, "Delete selected pixels"));
         }
 
         /// <summary>
@@ -75,8 +76,6 @@ namespace PixiEditor.Models.Controllers
                 }
 
                 UseTool(mouseMove, tool, Manager.PrimaryColor);
-
-                lastMousePos = newPos;
             }
         }
 
@@ -85,53 +84,37 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         public void ApplyPreviewLayer()
         {
-            if (previewLayerChanges == null)
-            {
-                return;
-            }
-
-            Layer[] layers = new Layer[previewLayerChanges.Count];
-
-            for (int i = 0; i < layers.Length; i++)
-            {
-                layers[i] = Manager.ActiveDocument.Layers.First(x => x.LayerGuid == previewLayerChanges[i].LayerGuid);
-            }
-
-            if (layers.Length > 0)
-            {
-                IEnumerable<LayerChange> oldValues =
-                    ApplyToLayers(layers, previewLayerChanges.ToArray());
-
-                foreach (var oldValue in oldValues)
-                {
-                    var previewChanges = previewLayerChanges.First(x => x.LayerGuid == oldValue.LayerGuid);
-
-                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
-                        previewChanges.PixelChanges,
-                        oldValue.PixelChanges,
-                        previewChanges.LayerGuid));
-                }
-
-                Manager.ActiveDocument.GeneratePreviewLayer();
-            }
-
-            previewLayerChanges = null;
+            var previewLayer = Manager.ActiveDocument.PreviewLayer;
+            var activeLayer = Manager.ActiveLayer;
+
+            Int32Rect dirtyRect = new Int32Rect(previewLayer.OffsetX, previewLayer.OffsetY, previewLayer.Width, previewLayer.Height);
+            activeLayer.DynamicResizeAbsolute(dirtyRect);
+            previewLayer.LayerBitmap.SkiaSurface.Draw(
+                    activeLayer.LayerBitmap.SkiaSurface.Canvas,
+                    previewLayer.OffsetX - activeLayer.OffsetX,
+                    previewLayer.OffsetY - activeLayer.OffsetY,
+                    BlendingPaint
+                );
+            Manager.ActiveLayer.InvokeLayerBitmapChange(dirtyRect);
+            // Don't forget about firing BitmapChanged
+            BitmapChanged?.Invoke(this, null);
+            Manager.ActiveDocument.PreviewLayer.Reset();
         }
 
-        private void UseTool(List<Coordinates> mouseMoveCords, BitmapOperationTool tool, Color color)
+        private void UseTool(List<Coordinates> mouseMoveCords, BitmapOperationTool tool, SKColor color)
         {
-            if(sizeSetting == null)
+            if (sizeSetting == null)
             {
                 sizeSetting = tool.Toolbar.GetSetting<SizeSetting>("ToolSize");
             }
 
             int thickness = sizeSetting != null ? sizeSetting.Value : 1;
 
-            bool shiftDown = Keyboard.IsKeyDown(Key.LeftShift);
-           
+            bool shiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
+
             if (shiftDown && tool.UsesShift)
             {
-                bool mouseInLine = MouseCordsNotInLine(mouseMoveCords, thickness);
+                bool mouseInLine = DoCoordsFormLine(mouseMoveCords, thickness);
 
                 if (!mouseInLine)
                 {
@@ -145,18 +128,10 @@ namespace PixiEditor.Models.Controllers
 
             if (!tool.RequiresPreviewLayer)
             {
-                LayerChange[] modifiedLayers = tool.Use(Manager.ActiveLayer, mouseMoveCords, color);
-                LayerChange[] oldPixelsValues = new LayerChange[modifiedLayers.Length];
-                for (int i = 0; i < modifiedLayers.Length; i++)
-                {
-                    Layer layer = Manager.ActiveDocument.Layers.First(x => x.LayerGuid == modifiedLayers[i].LayerGuid);
-                    oldPixelsValues[i] = ApplyToLayer(layer, modifiedLayers[i]);
-
-                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(
-                        modifiedLayers[i].PixelChanges,
-                        oldPixelsValues[i].PixelChanges,
-                        modifiedLayers[i].LayerGuid));
-                }
+                if (!Manager.ActiveDocument.PreviewLayer.IsReset)
+                    Manager.ActiveDocument.PreviewLayer.Reset();
+                tool.Use(Manager.ActiveLayer, mouseMoveCords, color);
+                BitmapChanged?.Invoke(this, null);
             }
             else
             {
@@ -164,39 +139,22 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        private LayerChange ApplyToLayer(Layer layer, LayerChange change)
+        private bool DoCoordsFormLine(List<Coordinates> coords, int thickness)
         {
-            return ApplyToLayers(new Layer[] { layer }, new LayerChange[] { change })[0];
-        }
-
-        private LayerChange[] ApplyToLayers(Layer[] layers, LayerChange[] changes)
-        {
-            LayerChange[] oldPixelValues = new LayerChange[changes.Length];
-            for (int i = 0; i < layers.Length; i++)
-            {
-                Layer layer = layers[i];
-                LayerChange change = changes.First(x => x.LayerGuid == layer.LayerGuid);
-                layer.DynamicResize(change.PixelChanges);
-
-                oldPixelValues[i] = new LayerChange(
-                GetOldPixelsValues(change.PixelChanges.ChangedPixels.Keys.ToArray()),
-                change.LayerGuid);
-            }
-
-            for (int i = 0; i < layers.Length; i++)
-            {
-                Layer layer = layers[i];
-                LayerChange change = changes.First(x => x.LayerGuid == layer.LayerGuid);
-                layer.SetPixels(change.PixelChanges, false);
-            }
-
-            return oldPixelValues;
-        }
-
-        private bool MouseCordsNotInLine(List<Coordinates> cords, int thickness)
-        {
-            return (cords[0].X > cords[^1].X - thickness && cords[0].X < cords[^1].X + thickness)
-                || (cords[0].Y > cords[^1].Y - thickness && cords[0].Y < cords[^1].Y + thickness);
+            var p1 = coords[0];
+            var p2 = coords[^1];
+            //find delta and mirror to first quadrant
+            float dX = Math.Abs(p2.X - p1.X);
+            float dY = Math.Abs(p2.Y - p1.Y);
+
+            //normalize
+            float length = (float)Math.Sqrt(dX * dX + dY * dY);
+            if (length == 0)
+                return false;
+            dX = dX / length;
+            dY = dY / length;
+
+            return dX < 0.25f || dY < 0.25f; //angle < 15 deg or angle > 75 deg (sin 15 ~= 0.25)
         }
 
         private List<Coordinates> GetLineCoordinates(List<Coordinates> mouseMoveCords, int thickness)
@@ -204,8 +162,7 @@ namespace PixiEditor.Models.Controllers
             int y = mouseMoveCords[0].Y;
             int x = mouseMoveCords[0].X;
 
-
-            if (Math.Abs(mouseMoveCords[^1].X - mouseMoveCords[0].X) - thickness > 0)
+            if (Math.Abs(mouseMoveCords[^1].X - mouseMoveCords[0].X) > Math.Abs(mouseMoveCords[^1].Y - mouseMoveCords[0].Y))
             {
                 y = mouseMoveCords[^1].Y;
             }
@@ -223,33 +180,42 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         private List<Coordinates> GetSquareCoordiantes(List<Coordinates> mouseMoveCords)
         {
-            int xLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
-            int yLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
-            if (mouseMoveCords[^1].Y > mouseMoveCords[0].Y)
-            {
-                xLength *= -1;
-            }
-
-            if (mouseMoveCords[^1].X > mouseMoveCords[0].X)
-            {
-                xLength *= -1;
-            }
-
-            mouseMoveCords[0] = new Coordinates(mouseMoveCords[^1].X + xLength, mouseMoveCords[^1].Y + yLength);
+            var p1 = mouseMoveCords[0];
+            var p2 = mouseMoveCords[^1];
+
+            //find delta and mirror to first quadrant
+            var dX = Math.Abs(p2.X - p1.X);
+            var dY = Math.Abs(p2.Y - p1.Y);
+
+            float sqrt2 = (float)Math.Sqrt(2);
+            //vector of length 1 at 45 degrees;
+            float diagX, diagY;
+            diagX = diagY = 1 / sqrt2;
+
+            //dot product of delta and diag, returns length of [delta projected onto diag]
+            float projectedLength = diagX * dX + diagY * dY;
+            //project above onto axes
+            float axisLength = projectedLength / sqrt2;
+
+            //final coords
+            float x = -Math.Sign(p2.X - p1.X) * axisLength;
+            float y = -Math.Sign(p2.Y - p1.Y) * axisLength;
+            mouseMoveCords[0] = new Coordinates((int)x + p2.X, (int)y + p2.Y);
             return mouseMoveCords;
         }
 
         private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
         {
-            Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
-            using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
+            Dictionary<Coordinates, SKColor> values = new Dictionary<Coordinates, SKColor>();
+            //using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
             {
                 Coordinates[] relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
                 for (int i = 0; i < coordinates.Length; i++)
                 {
+                    var cl = Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y);
                     values.Add(
                         coordinates[i],
-                        Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y));
+                        cl);
                 }
             }
 
@@ -258,46 +224,18 @@ namespace PixiEditor.Models.Controllers
 
         private void UseToolOnPreviewLayer(List<Coordinates> mouseMove, bool clearPreviewLayer = true)
         {
-            LayerChange[] modifiedLayers;
-            if (mouseMove.Count > 0 && mouseMove[0] != lastMousePos)
+            if (mouseMove.Count > 0)
             {
-                if (clearPreviewLayer || Manager.ActiveDocument.PreviewLayer == null)
+                if (clearPreviewLayer)
                 {
-                    Manager.ActiveDocument.GeneratePreviewLayer();
+                    Manager.ActiveDocument.PreviewLayer.ClearCanvas();
                 }
 
-                modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
-                    Manager.ActiveDocument.ActiveLayer,
+                ((BitmapOperationTool)Tools.ActiveTool).Use(
+                    Manager.ActiveDocument.PreviewLayer,
                     mouseMove,
                     Manager.PrimaryColor);
-
-                BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
-                if (changes.Length == 0)
-                {
-                    return;
-                }
-
-                Manager.ActiveDocument.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
-
-                if (clearPreviewLayer || previewLayerChanges == null)
-                {
-                    previewLayerChanges = new List<LayerChange>(modifiedLayers);
-                }
-                else
-                {
-                    InjectPreviewLayerChanges(modifiedLayers);
-                }
-            }
-        }
-
-        private void InjectPreviewLayerChanges(LayerChange[] modifiedLayers)
-        {
-            for (int i = 0; i < modifiedLayers.Length; i++)
-            {
-                var layer = previewLayerChanges.First(x => x.LayerGuid == modifiedLayers[i].LayerGuid);
-                layer.PixelChanges.ChangedPixels.AddRangeOverride(modifiedLayers[i].PixelChanges.ChangedPixels);
-                layer.PixelChanges = layer.PixelChanges.WithoutTransparentPixels();
             }
         }
     }
-}
+}

+ 227 - 47
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -1,39 +1,88 @@
-using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Exceptions;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
+using PixiEditor.Parser;
 using PixiEditor.ViewModels;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.IO;
 using System.Linq;
 using System.Windows;
+using System.Windows.Media;
 using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.Controllers
 {
     public static class ClipboardController
     {
+        public static readonly string TempCopyFilePath = Path.Join(
+                    Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+                    "PixiEditor",
+                    "Copied.png");
+
         /// <summary>
-        ///     Copies selection to clipboard in PNG, Bitmap and DIB formats.
+        ///     Copies the selection to clipboard in PNG, Bitmap and DIB formats.
         /// </summary>
         /// <param name="layers">Layers where selection is.</param>
-        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight)
+        public static void CopyToClipboard(Layer[] layers, Coordinates[] selection, int originalImageWidth, int originalImageHeight, SerializableDocument document = null)
         {
             Clipboard.Clear();
-            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(originalImageWidth, originalImageHeight, layers);
-            using (MemoryStream pngStream = new MemoryStream())
+            using Surface surface = BitmapUtils.CombineLayers(new Int32Rect(0, 0, originalImageWidth, originalImageHeight), layers);
+            DataObject data = new DataObject();
+
+            WriteableBitmap combinedBitmaps = surface.ToWriteableBitmap();
+            BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection, out int offsetX, out int offsetY, out int width, out int height);
+            data.SetData(typeof(CropData), new CropData(width, height, offsetX, offsetY).ToStream());
+
+            using (SKData pngData = surface.SkiaSurface.Snapshot(SKRectI.Create(offsetX, offsetY, width, height)).Encode())
             {
-                DataObject data = new DataObject();
-                BitmapSource croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection);
-                data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency support
+                // Stream should not be disposed
+                MemoryStream pngStream = new MemoryStream();
+                pngData.AsStream().CopyTo(pngStream);
 
-                PngBitmapEncoder encoder = new PngBitmapEncoder();
-                encoder.Frames.Add(BitmapFrame.Create(croppedBmp));
-                encoder.Save(pngStream);
                 data.SetData("PNG", pngStream, false); // PNG, supports transparency
 
-                Clipboard.SetImage(croppedBmp); // DIB format
+                pngStream.Position = 0;
+                Directory.CreateDirectory(Path.GetDirectoryName(TempCopyFilePath));
+                using FileStream fileStream = new FileStream(TempCopyFilePath, FileMode.Create, FileAccess.Write);
+                pngStream.CopyTo(fileStream);
+                data.SetFileDropList(new StringCollection() { TempCopyFilePath });
+            }
+
+            data.SetData(DataFormats.Bitmap, croppedBmp, true); // Bitmap, no transparency
+            data.SetImage(croppedBmp); // DIB format, no transparency
+
+            if (document != null)
+            {
+                MemoryStream memoryStream = new();
+                PixiParser.Serialize(document, memoryStream);
+                data.SetData("PIXI", memoryStream); // PIXI, supports transparency, layers, groups and swatches
                 Clipboard.SetDataObject(data, true);
             }
+
+            Clipboard.SetDataObject(data, true);
+        }
+
+        /// <summary>
+        /// Copies the selection to clipboard in PNG, Bitmap and DIB formats. <para/>
+        /// Also serailizes the <paramref name="document"/> in the PIXI format and copies it to the clipboard.
+        /// </summary>
+        public static void CopyToClipboard(Document document)
+        {
+            CopyToClipboard(
+                document.Layers.Where(x => document.GetFinalLayerIsVisible(x)).ToArray(),
+                document.ActiveSelection.SelectedPoints.ToArray(),
+                //new Coordinates[] { (0, 0), (15, 15) },
+                document.Width,
+                document.Height,
+                document.ToSerializable());
         }
 
         /// <summary>
@@ -41,46 +90,81 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         public static void PasteFromClipboard()
         {
-            WriteableBitmap image = GetImageFromClipboard();
-            if (image != null)
+            var layers = GetLayersFromClipboard();
+
+            Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
+            int startIndex = activeDocument.Layers.Count;
+
+            foreach (var layer in layers)
             {
-                AddImageToLayers(image);
-                int latestLayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Count - 1;
-                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
-                    new Change(RemoveLayerProcess, new object[] { latestLayerIndex }, AddLayerProcess, new object[] { image }));
+                activeDocument.Layers.Add(layer);
             }
-        }
 
+            activeDocument.UndoManager.AddUndoChange(
+                new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
+        }
 
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
         /// <returns>WriteableBitmap.</returns>
-        public static WriteableBitmap GetImageFromClipboard()
+        public static IEnumerable<Layer> GetLayersFromClipboard()
         {
-            DataObject dao = (DataObject)Clipboard.GetDataObject();
-            WriteableBitmap finalImage = null;
-            if (dao.GetDataPresent("PNG"))
+            DataObject data = (DataObject)Clipboard.GetDataObject();
+
+            if (data.GetDataPresent("PIXI"))
             {
-                using (MemoryStream pngStream = dao.GetData("PNG") as MemoryStream)
+                SerializableDocument document = GetSerializable(data, out CropData crop);
+                SKRectI cropRect = SKRectI.Create(crop.OffsetX, crop.OffsetY, crop.Width, crop.Height);
+
+                foreach (SerializableLayer sLayer in document)
                 {
-                    if (pngStream != null)
+                    SKRectI intersect;
+
+                    if (/*layer.OffsetX > crop.OffsetX + crop.Width || layer.OffsetY > crop.OffsetY + crop.Height ||*/
+                        !sLayer.IsVisible || sLayer.Opacity == 0 ||
+                        (intersect = SKRectI.Intersect(cropRect, sLayer.GetRect())) == SKRectI.Empty)
                     {
-                        PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
-                        finalImage = new WriteableBitmap(decoder.Frames[0].Clone());
+                        continue;
                     }
+
+                    var layer = sLayer.ToLayer();
+
+                    layer.Crop(intersect);
+
+                    yield return layer;
                 }
             }
-            else if (dao.GetDataPresent(DataFormats.Dib))
+            else if (TryFromSingleImage(data, out Surface singleImage))
             {
-                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
+                yield return new Layer("Image", singleImage);
             }
-            else if (dao.GetDataPresent(DataFormats.Bitmap))
+            else if (data.GetDataPresent(DataFormats.FileDrop))
             {
-                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
-            }
+                foreach (string path in data.GetFileDropList())
+                {
+                    if (!Importer.IsSupportedFile(path))
+                    {
+                        continue;
+                    }
+
+                    Layer layer = null;
+
+                    try
+                    {
+                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
+                    }
+                    catch (CorruptedFileException)
+                    {
+                    }
 
-            return finalImage;
+                    yield return layer ?? new($"Corrupt {path}");
+                }
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
         }
 
         public static bool IsImageInClipboard()
@@ -91,42 +175,138 @@ namespace PixiEditor.Models.Controllers
                 return false;
             }
 
+            var files = dao.GetFileDropList();
+
+            if (files != null)
+            {
+                foreach (var file in files)
+                {
+                    if (Importer.IsSupportedFile(file))
+                    {
+                        return true;
+                    }
+                }
+            }
+
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
-                   dao.GetDataPresent(DataFormats.Bitmap);
+                   dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
+                   dao.GetDataPresent("PIXI");
         }
 
-        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection)
+        public static BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection, out int offsetX, out int offsetY, out int width, out int height)
         {
-            int offsetX = selection.Min(x => x.X);
-            int offsetY = selection.Min(x => x.Y);
-            int width = selection.Max(x => x.X) - offsetX + 1;
-            int height = selection.Max(x => x.Y) - offsetY + 1;
+            offsetX = selection.Min(min => min.X);
+            offsetY = selection.Min(min => min.Y);
+            width = selection.Max(max => max.X) - offsetX + 1;
+            height = selection.Max(max => max.Y) - offsetY + 1;
             return bitmap.Crop(offsetX, offsetY, width, height);
         }
 
-        private static void RemoveLayerProcess(object[] parameters)
+        private static BitmapSource FromPNG(DataObject data)
+        {
+            MemoryStream pngStream = data.GetData("PNG") as MemoryStream;
+            PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
+
+            return decoder.Frames[0];
+        }
+
+        private static unsafe SerializableDocument GetSerializable(DataObject data, out CropData cropData)
+        {
+            MemoryStream pixiStream = data.GetData("PIXI") as MemoryStream;
+            SerializableDocument document = PixiParser.Deserialize(pixiStream);
+
+            if (data.GetDataPresent(typeof(CropData)))
+            {
+                cropData = CropData.FromStream(data.GetData(typeof(CropData)) as MemoryStream);
+            }
+            else
+            {
+                cropData = new CropData(document.Width, document.Height, 0, 0);
+            }
+
+            return document;
+        }
+
+        private static bool TryFromSingleImage(DataObject data, out Surface result)
+        {
+            try
+            {
+                BitmapSource source;
+
+                if (data.GetDataPresent("PNG"))
+                {
+                    source = FromPNG(data);
+                }
+                else if (data.GetDataPresent(DataFormats.Dib) || data.GetDataPresent(DataFormats.Bitmap))
+                {
+                    source = Clipboard.GetImage();
+                }
+                else
+                {
+                    result = null;
+                    return false;
+                }
+
+                if (source.Format.IsSkiaSupported())
+                {
+                    result = new Surface(source);
+                }
+                else
+                {
+                    FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
+                    newFormat.BeginInit();
+                    newFormat.Source = source;
+                    newFormat.DestinationFormat = PixelFormats.Rgba64;
+                    newFormat.EndInit();
+
+                    result = new Surface(newFormat);
+                }
+
+                return true;
+            }
+            catch { }
+
+            result = null;
+            return false;
+        }
+
+        private static void RemoveLayersProcess(object[] parameters)
         {
-            if (parameters.Length == 0 || !(parameters[0] is int))
+            if (parameters.Length == 0 || parameters[0] is not int i)
             {
                 return;
             }
 
-            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0], true);
+            Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
+
+            while (i < document.Layers.Count)
+            {
+                document.RemoveLayer(i, true);
+            }
         }
 
-        private static void AddLayerProcess(object[] parameters)
+        private static void AddLayersProcess(object[] parameters)
         {
-            if (parameters.Length == 0 || !(parameters[0] is WriteableBitmap))
+            if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
             {
                 return;
             }
 
-            AddImageToLayers((WriteableBitmap)parameters[0]);
+            foreach (var layer in layers)
+            {
+                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
+            }
         }
 
-        private static void AddImageToLayers(WriteableBitmap image)
+        private static void DisposeProcess(object[] rev, object[] proc)
         {
-            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer("Image", image);
+            if (proc[0] is IEnumerable<Layer> layers)
+            {
+                foreach (var layer in layers)
+                {
+                    layer.LayerBitmap.Dispose();
+                }
+            }
         }
     }
 }

+ 168 - 0
PixiEditor/Models/Controllers/LayerStackRenderer.cs

@@ -0,0 +1,168 @@
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers.Utils;
+using SkiaSharp;
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class LayerStackRenderer : INotifyPropertyChanged, IDisposable
+    {
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        private SKPaint ClearPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
+
+        private ObservableCollection<Layer> layers;
+        private LayerStructure structure;
+
+        private Surface finalSurface;
+        private SKSurface backingSurface;
+        private WriteableBitmap finalBitmap;
+        public WriteableBitmap FinalBitmap
+        {
+            get => finalBitmap;
+            set
+            {
+                finalBitmap = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FinalBitmap)));
+            }
+        }
+
+        public Surface FinalSurface { get => finalSurface; }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        public LayerStackRenderer(ObservableCollection<Layer> layers, LayerStructure structure, int width, int height)
+        {
+            this.layers = layers;
+            this.structure = structure;
+            layers.CollectionChanged += OnLayersChanged;
+            SubscribeToAllLayers(layers);
+            Resize(width, height);
+        }
+
+        public void Resize(int newWidth, int newHeight)
+        {
+            finalSurface?.Dispose();
+            backingSurface?.Dispose();
+            finalSurface = new Surface(newWidth, newHeight);
+            FinalBitmap = new WriteableBitmap(newWidth, newHeight, 96, 96, PixelFormats.Pbgra32, null);
+            var imageInfo = new SKImageInfo(newWidth, newHeight, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
+            backingSurface = SKSurface.Create(imageInfo, finalBitmap.BackBuffer, finalBitmap.BackBufferStride);
+            Update(new Int32Rect(0, 0, newWidth, newHeight));
+        }
+
+        public void SetNewLayersCollection(ObservableCollection<Layer> layers)
+        {
+            layers.CollectionChanged -= OnLayersChanged;
+            UnsubscribeFromAllLayers(this.layers);
+            this.layers = layers;
+            SubscribeToAllLayers(layers);
+            layers.CollectionChanged += OnLayersChanged;
+            Update(new Int32Rect(0, 0, finalSurface.Width, finalSurface.Height));
+        }
+
+        public void Dispose()
+        {
+            finalSurface.Dispose();
+            backingSurface.Dispose();
+            BlendingPaint.Dispose();
+            ClearPaint.Dispose();
+            layers.CollectionChanged -= OnLayersChanged;
+        }
+
+        private void SubscribeToAllLayers(ObservableCollection<Layer> layers)
+        {
+            foreach (var layer in layers)
+            {
+                layer.LayerBitmapChanged += OnLayerBitmapChanged;
+            }
+        }
+
+        private void UnsubscribeFromAllLayers(ObservableCollection<Layer> layers)
+        {
+            foreach (var layer in layers)
+            {
+                layer.LayerBitmapChanged -= OnLayerBitmapChanged;
+            }
+        }
+
+        private void Update(Int32Rect dirtyRectangle)
+        {
+            dirtyRectangle = dirtyRectangle.Intersect(new Int32Rect(0, 0, finalBitmap.PixelWidth, finalBitmap.PixelHeight));
+            finalSurface.SkiaSurface.Canvas.DrawRect(
+                new SKRect(
+                    dirtyRectangle.X, dirtyRectangle.Y,
+                    dirtyRectangle.X + dirtyRectangle.Width,
+                    dirtyRectangle.Y + dirtyRectangle.Height
+                    ),
+                ClearPaint
+                );
+            foreach (var layer in layers)
+            {
+                if (!LayerStructureUtils.GetFinalLayerIsVisible(layer, structure))
+                    continue;
+                BlendingPaint.Color = new SKColor(255, 255, 255, (byte)(LayerStructureUtils.GetFinalLayerOpacity(layer, structure) * 255));
+
+                Int32Rect layerRect = new Int32Rect(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height);
+                Int32Rect layerPortion = layerRect.Intersect(dirtyRectangle);
+
+                using (var snapshot = layer.LayerBitmap.SkiaSurface.Snapshot())
+                {
+                    finalSurface.SkiaSurface.Canvas.DrawImage(
+                        snapshot,
+                        new SKRect(
+                            layerPortion.X - layer.OffsetX,
+                            layerPortion.Y - layer.OffsetY,
+                            layerPortion.X - layer.OffsetX + layerPortion.Width,
+                            layerPortion.Y - layer.OffsetY + layerPortion.Height),
+                        new SKRect(
+                            layerPortion.X,
+                            layerPortion.Y,
+                            layerPortion.X + layerPortion.Width,
+                            layerPortion.Y + layerPortion.Height
+                        ),
+                        BlendingPaint);
+                }
+            }
+            finalBitmap.Lock();
+            using (var snapshot = finalSurface.SkiaSurface.Snapshot())
+            {
+                SKRect rect = new(dirtyRectangle.X, dirtyRectangle.Y, dirtyRectangle.X + dirtyRectangle.Width, dirtyRectangle.Y + dirtyRectangle.Height);
+                backingSurface.Canvas.DrawImage(snapshot, rect, rect, Surface.ReplacingPaint);
+            }
+
+            finalBitmap.AddDirtyRect(dirtyRectangle);
+            finalBitmap.Unlock();
+        }
+
+        private void OnLayersChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+        {
+            if (e.NewItems != null)
+            {
+                foreach (var obj in e.NewItems)
+                {
+                    Layer layer = (Layer)obj;
+                    layer.LayerBitmapChanged += OnLayerBitmapChanged;
+                }
+            }
+            if (e.OldItems != null)
+            {
+                foreach (var obj in e.OldItems)
+                {
+                    ((Layer)obj).LayerBitmapChanged -= OnLayerBitmapChanged;
+                }
+            }
+            Update(new Int32Rect(0, 0, finalSurface.Width, finalSurface.Height));
+        }
+
+        private void OnLayerBitmapChanged(object sender, Int32Rect e)
+        {
+            Update(e);
+        }
+    }
+}

+ 14 - 3
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -1,7 +1,7 @@
-using System;
+using PixiEditor.Models.Position;
+using System;
 using System.Collections.Generic;
 using System.Windows.Input;
-using PixiEditor.Models.Position;
 
 namespace PixiEditor.Models.Controllers
 {
@@ -10,6 +10,7 @@ namespace PixiEditor.Models.Controllers
         public event EventHandler StartedRecordingChanges;
 
         public event EventHandler<MouseEventArgs> OnMouseDown;
+        public event EventHandler<MouseMovementEventArgs> OnMouseDownCoordinates;
 
         public event EventHandler<MouseEventArgs> OnMouseUp;
 
@@ -17,12 +18,15 @@ namespace PixiEditor.Models.Controllers
 
         public event EventHandler StoppedRecordingChanges;
 
+        public MouseButtonState LeftMouseState { get; private set; }
+
         public List<Coordinates> LastMouseMoveCoordinates { get; set; } = new List<Coordinates>();
 
         public bool IsRecordingChanges { get; private set; }
 
         public bool ClickedOnCanvas { get; set; }
 
+
         public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
         {
             if (IsRecordingChanges == false)
@@ -59,14 +63,21 @@ namespace PixiEditor.Models.Controllers
         /// </summary>
         public void MouseDown(MouseEventArgs args)
         {
+            LeftMouseState = args.LeftButton;
             OnMouseDown?.Invoke(this, args);
         }
 
+        public void MouseDownCoordinates(Coordinates mouseCoordinates)
+        {
+            OnMouseDownCoordinates?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
+        }
+
         /// <summary>
         /// Plain mouse up, does not affect mouse recordings.
         /// </summary>
         public void MouseUp(MouseEventArgs args)
         {
+            LeftMouseState = MouseButtonState.Released;
             OnMouseUp?.Invoke(this, args);
         }
 
@@ -80,4 +91,4 @@ namespace PixiEditor.Models.Controllers
             }
         }
     }
-}
+}

+ 0 - 105
PixiEditor/Models/Controllers/PixelChangesController.cs

@@ -1,105 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using PixiEditor.Models.DataHolders;
-
-namespace PixiEditor.Models.Controllers
-{
-    public class PixelChangesController
-    {
-        private Dictionary<Guid, LayerChange> LastChanges { get; set; }
-
-        private Dictionary<Guid, LayerChange> LastOldValues { get; set; }
-
-        /// <summary>
-        ///     Adds layer changes to controller.
-        /// </summary>
-        /// <param name="changes">New changes.</param>
-        /// <param name="oldValues">Old values of changes.</param>
-        public void AddChanges(LayerChange changes, LayerChange oldValues)
-        {
-            if (changes.PixelChanges.ChangedPixels.Count > 0)
-            {
-                if (LastChanges == null)
-                {
-                    LastChanges = new Dictionary<Guid, LayerChange> { { changes.LayerGuid, changes } };
-                    LastOldValues = new Dictionary<Guid, LayerChange> { { oldValues.LayerGuid, oldValues } };
-                }
-                else if (LastChanges.ContainsKey(changes.LayerGuid))
-                {
-                    AddToExistingLayerChange(changes, oldValues);
-                }
-                else
-                {
-                    AddNewLayerChange(changes, oldValues);
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Returns all changes and deletes them from controller.
-        /// </summary>
-        /// <returns>Tuple array with new changes and old values.</returns>
-        public Tuple<LayerChange, LayerChange>[] PopChanges()
-        {
-            // Maybe replace Tuple with custom data type
-            if (LastChanges == null)
-            {
-                return null;
-            }
-
-            Tuple<LayerChange, LayerChange>[] result = new Tuple<LayerChange, LayerChange>[LastChanges.Count];
-            int i = 0;
-            foreach (KeyValuePair<Guid, LayerChange> change in LastChanges)
-            {
-                Dictionary<Position.Coordinates, System.Windows.Media.Color> pixelChanges =
-                    change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
-                Dictionary<Position.Coordinates, System.Windows.Media.Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
-                    .ToDictionary(entry => entry.Key, entry => entry.Value);
-
-                LayerChange tmp = new LayerChange(new BitmapPixelChanges(pixelChanges), change.Key);
-                LayerChange oldValuesTmp = new LayerChange(new BitmapPixelChanges(oldValues), change.Key);
-
-                result[i] = new Tuple<LayerChange, LayerChange>(tmp, oldValuesTmp);
-                i++;
-            }
-
-            LastChanges = null;
-            LastOldValues = null;
-            return result;
-        }
-
-        private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
-        {
-            LastChanges[changes.LayerGuid] = changes;
-            LastOldValues[changes.LayerGuid] = oldValues;
-        }
-
-        private void AddToExistingLayerChange(LayerChange layerChange, LayerChange oldValues)
-        {
-            foreach (KeyValuePair<Position.Coordinates, System.Windows.Media.Color> change in layerChange.PixelChanges.ChangedPixels)
-            {
-                if (LastChanges[layerChange.LayerGuid].PixelChanges.ChangedPixels.ContainsKey(change.Key))
-                {
-                    continue;
-                }
-                else
-                {
-                    LastChanges[layerChange.LayerGuid].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
-                }
-            }
-
-            foreach (KeyValuePair<Position.Coordinates, System.Windows.Media.Color> change in oldValues.PixelChanges.ChangedPixels)
-            {
-                if (LastOldValues[layerChange.LayerGuid].PixelChanges.ChangedPixels.ContainsKey(change.Key))
-                {
-                    continue;
-                }
-                else
-                {
-                    LastOldValues[layerChange.LayerGuid].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
-                }
-            }
-        }
-    }
-}

+ 101 - 0
PixiEditor/Models/Controllers/SingleLayerRenderer.cs

@@ -0,0 +1,101 @@
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Layers;
+using SkiaSharp;
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class SingleLayerRenderer : INotifyPropertyChanged, IDisposable
+    {
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        private SKPaint ClearPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src, Color = SKColors.Transparent };
+        private Layer layer;
+
+        private SKSurface backingSurface;
+        private WriteableBitmap finalBitmap;
+        public WriteableBitmap FinalBitmap
+        {
+            get => finalBitmap;
+            set
+            {
+                finalBitmap = value;
+                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FinalBitmap)));
+            }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+        public SingleLayerRenderer(Layer layer, int width, int height)
+        {
+            this.layer = layer;
+            layer.LayerBitmapChanged += OnLayerBitmapChanged;
+            Resize(width, height);
+        }
+
+        public void Resize(int newWidth, int newHeight)
+        {
+            backingSurface?.Dispose();
+
+            finalBitmap = new WriteableBitmap(newWidth, newHeight, 96, 96, PixelFormats.Pbgra32, null);
+            var imageInfo = new SKImageInfo(newWidth, newHeight, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
+            backingSurface = SKSurface.Create(imageInfo, finalBitmap.BackBuffer, finalBitmap.BackBufferStride);
+            Update(new Int32Rect(0, 0, newWidth, newHeight));
+        }
+
+        public void Dispose()
+        {
+            backingSurface.Dispose();
+            BlendingPaint.Dispose();
+            layer.LayerBitmapChanged -= OnLayerBitmapChanged;
+        }
+
+        private void Update(Int32Rect dirtyRectangle)
+        {
+            dirtyRectangle = dirtyRectangle.Intersect(new Int32Rect(0, 0, finalBitmap.PixelWidth, finalBitmap.PixelHeight));
+            if (!dirtyRectangle.HasArea)
+                return;
+            backingSurface.Canvas.DrawRect(
+                new SKRect(
+                    dirtyRectangle.X, dirtyRectangle.Y,
+                    dirtyRectangle.X + dirtyRectangle.Width,
+                    dirtyRectangle.Y + dirtyRectangle.Height
+                    ),
+                ClearPaint
+                );
+            finalBitmap.Lock();
+            if (layer.IsVisible)
+            {
+                BlendingPaint.Color = new SKColor(255, 255, 255, (byte)(layer.Opacity * 255));
+                var layerDirty = dirtyRectangle.Intersect(new Int32Rect(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height));
+                using (var snapshot = layer.LayerBitmap.SkiaSurface.Snapshot())
+                {
+                    backingSurface.Canvas.DrawImage(
+                        snapshot,
+                        new SKRect(
+                            layerDirty.X - layer.OffsetX,
+                            layerDirty.Y - layer.OffsetY,
+                            layerDirty.X - layer.OffsetX + layerDirty.Width,
+                            layerDirty.Y - layer.OffsetY + layerDirty.Height),
+                        new SKRect(
+                            layerDirty.X,
+                            layerDirty.Y,
+                            layerDirty.X + layerDirty.Width,
+                            layerDirty.Y + layerDirty.Height
+                        ),
+                        BlendingPaint);
+                }
+            }
+
+            finalBitmap.AddDirtyRect(dirtyRectangle);
+            finalBitmap.Unlock();
+        }
+
+        private void OnLayerBitmapChanged(object sender, Int32Rect e)
+        {
+            Update(e);
+        }
+    }
+}

+ 39 - 0
PixiEditor/Models/Controllers/SurfaceRenderer.cs

@@ -0,0 +1,39 @@
+using PixiEditor.Models.DataHolders;
+using SkiaSharp;
+using System;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PixiEditor.Models.Controllers
+{
+    class SurfaceRenderer : IDisposable
+    {
+        public SKSurface BackingSurface { get; private set; }
+        public WriteableBitmap FinalBitmap { get; private set; }
+        private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        public SurfaceRenderer(int width, int height)
+        {
+            FinalBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
+            var imageInfo = new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
+            BackingSurface = SKSurface.Create(imageInfo, FinalBitmap.BackBuffer, FinalBitmap.BackBufferStride);
+        }
+
+        public void Dispose()
+        {
+            BackingSurface.Dispose();
+            BlendingPaint.Dispose();
+        }
+
+        public void Draw(Surface otherSurface, byte opacity)
+        {
+            BackingSurface.Canvas.Clear();
+            FinalBitmap.Lock();
+            BlendingPaint.Color = new SKColor(255, 255, 255, opacity);
+            using (var snapshot = otherSurface.SkiaSurface.Snapshot())
+                BackingSurface.Canvas.DrawImage(snapshot, new SKRect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
+            FinalBitmap.AddDirtyRect(new Int32Rect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
+            FinalBitmap.Unlock();
+        }
+    }
+}

+ 11 - 1
PixiEditor/Models/Controllers/UndoManager.cs

@@ -9,7 +9,7 @@ using PixiEditor.ViewModels;
 namespace PixiEditor.Models.Controllers
 {
     [DebuggerDisplay("{UndoStack.Count} undo steps, {RedoStack.Count} redo step(s)")]
-    public class UndoManager
+    public class UndoManager : IDisposable
     {
         private bool lastChangeWasUndo;
 
@@ -158,6 +158,16 @@ namespace PixiEditor.Models.Controllers
             AddUndoChange(change);
         }
 
+        public void Dispose()
+        {
+            foreach (Change change in UndoStack.Concat(RedoStack))
+            {
+                change.Dispose();
+            }
+
+            GC.SuppressFinalize(this);
+        }
+
         private bool ChangeIsBlockedProperty(Change change)
         {
             return (change.Root != null || change.FindRootProcess != null)

+ 15 - 19
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -1,38 +1,34 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using PixiEditor.Exceptions;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
 
 namespace PixiEditor.Models.DataHolders
 {
     public struct BitmapPixelChanges
     {
-        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
+        public BitmapPixelChanges(Dictionary<Coordinates, SKColor> changedPixels)
         {
             ChangedPixels = changedPixels;
             WasBuiltAsSingleColored = false;
         }
 
-        public static BitmapPixelChanges Empty => new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+        public static BitmapPixelChanges Empty => new BitmapPixelChanges(new Dictionary<Coordinates, SKColor>());
 
         public bool WasBuiltAsSingleColored { get; private set; }
 
-        public Dictionary<Coordinates, Color> ChangedPixels { get; set; }
+        public Dictionary<Coordinates, SKColor> ChangedPixels { get; set; }
 
         /// <summary>
         ///     Builds BitmapPixelChanges with only one color for specified coordinates.
         /// </summary>
         /// <returns>Single-colored BitmapPixelChanges.</returns>
-        public static BitmapPixelChanges FromSingleColoredArray(IEnumerable<Coordinates> coordinates, Color color)
+        public static BitmapPixelChanges FromSingleColoredArray(IEnumerable<Coordinates> coordinates, SKColor color)
         {
-            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+            Dictionary<Coordinates, SKColor> dict = new Dictionary<Coordinates, SKColor>();
             foreach (Coordinates coordinate in coordinates)
             {
                 if (dict.ContainsKey(coordinate))
@@ -76,16 +72,16 @@ namespace PixiEditor.Models.DataHolders
         /// <summary>
         ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors.
         /// </summary>
-        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<Color> color)
+        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<SKColor> color)
         {
             Coordinates[] coordinateArray = coordinates.ToArray();
-            Color[] colorArray = color.ToArray();
+            SKColor[] colorArray = color.ToArray();
             if (coordinateArray.Length != colorArray.Length)
             {
                 throw new ArrayLengthMismatchException();
             }
 
-            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+            Dictionary<Coordinates, SKColor> dict = new Dictionary<Coordinates, SKColor>();
             for (int i = 0; i < coordinateArray.Length; i++)
             {
                 dict.Add(coordinateArray[i], colorArray[i]);
@@ -96,7 +92,7 @@ namespace PixiEditor.Models.DataHolders
 
         public BitmapPixelChanges WithoutTransparentPixels()
         {
-            return new BitmapPixelChanges(ChangedPixels.Where(x => x.Value.A > 0).ToDictionary(y => y.Key, y => y.Value));
+            return new BitmapPixelChanges(ChangedPixels.Where(x => x.Value.Alpha > 0).ToDictionary(y => y.Key, y => y.Value));
         }
     }
-}
+}

+ 11 - 2
PixiEditor/Models/DataHolders/Document/Document.Constructors.cs

@@ -1,6 +1,7 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels;
+using System;
 using System.Linq;
 
 namespace PixiEditor.Models.DataHolders
@@ -10,9 +11,12 @@ namespace PixiEditor.Models.DataHolders
         public Document(int width, int height)
             : this()
         {
+            if (width <= 0 || height <= 0)
+                throw new ArgumentException("Document dimensions must be greater than 0");
             Width = width;
             Height = height;
-            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(0, 0, width, height));
+            Renderer = new LayerStackRenderer(layers, layerStructure, Width, Height);
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(1, 1, width, height));
         }
 
         private Document()
@@ -25,6 +29,11 @@ namespace PixiEditor.Models.DataHolders
             Layers.CollectionChanged += Layers_CollectionChanged;
             LayerStructure.Groups.CollectionChanged += Groups_CollectionChanged;
             LayerStructure.LayerStructureChanged += LayerStructure_LayerStructureChanged;
+            DocumentSizeChanged += (sender, args) =>
+            {
+                Renderer.Resize(args.NewWidth, args.NewHeight);
+                GeneratePreviewLayer();
+            };
         }
 
         private void LayerStructure_LayerStructureChanged(object sender, LayerStructureChangedEventArgs e)
@@ -51,4 +60,4 @@ namespace PixiEditor.Models.DataHolders
             RaisePropertyChanged(nameof(Layers));
         }
     }
-}
+}

+ 2 - 1
PixiEditor/Models/DataHolders/Document/Document.Discord.cs

@@ -1,4 +1,5 @@
-using System;
+using PixiEditor.Parser;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;

+ 70 - 40
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -1,10 +1,11 @@
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
-using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers.Utils;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
+using SkiaSharp;
 using System;
 using System.Buffers;
 using System.Collections.Generic;
@@ -12,8 +13,6 @@ using System.Collections.ObjectModel;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -34,19 +33,51 @@ namespace PixiEditor.Models.DataHolders
             {
                 layers = value;
                 Layers.CollectionChanged += Layers_CollectionChanged;
+                Renderer.SetNewLayersCollection(value);
             }
         }
 
         public LayerStructure LayerStructure
         {
             get => layerStructure;
-            set
+            private set
             {
                 layerStructure = value;
                 RaisePropertyChanged(nameof(LayerStructure));
             }
         }
 
+        private LayerStackRenderer renderer;
+        public LayerStackRenderer Renderer
+        {
+            get => renderer;
+            private set
+            {
+                renderer = value;
+                RaisePropertyChanged(nameof(Renderer));
+            }
+        }
+
+        private Layer referenceLayer;
+        private SingleLayerRenderer referenceLayerRenderer;
+        public Layer ReferenceLayer
+        {
+            get => referenceLayer;
+            set
+            {
+                referenceLayer = value;
+                referenceLayerRenderer?.Dispose();
+                referenceLayerRenderer = referenceLayer == null ? null : new SingleLayerRenderer(referenceLayer, referenceLayer.Width, referenceLayer.Height);
+                RaisePropertyChanged(nameof(ReferenceLayer));
+                RaisePropertyChanged(nameof(ReferenceLayerRenderer));
+            }
+        }
+
+        public SingleLayerRenderer ReferenceLayerRenderer
+        {
+            get => referenceLayerRenderer;
+        }
+
         public Layer ActiveLayer => Layers.Count > 0 ? Layers.FirstOrDefault(x => x.LayerGuid == ActiveLayerGuid) : null;
 
         public Guid ActiveLayerGuid
@@ -90,30 +121,7 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         /// <param name="layer">Layer to check.</param>
         /// <returns>True if is visible, false if at least parent is not visible or layer itself is invisible.</returns>
-        public bool GetFinalLayerIsVisible(Layer layer)
-        {
-            if (!layer.IsVisible)
-            {
-                return false;
-            }
-
-            var group = LayerStructure.GetGroupByLayer(layer.LayerGuid);
-            bool atLeastOneParentIsInvisible = false;
-            GuidStructureItem groupToCheck = group;
-            while (groupToCheck != null)
-            {
-                if (!groupToCheck.IsVisible)
-                {
-                    atLeastOneParentIsInvisible = true;
-                    break;
-                }
-
-                groupToCheck = groupToCheck.Parent;
-            }
-
-            return !atLeastOneParentIsInvisible;
-        }
-
+        public bool GetFinalLayerIsVisible(Layer layer) => LayerStructureUtils.GetFinalLayerIsVisible(layer, LayerStructure);
         public void UpdateLayersColor()
         {
             foreach (var layer in Layers)
@@ -190,26 +198,33 @@ namespace PixiEditor.Models.DataHolders
             UndoManager.SquashUndoChanges(2, "Move group");
         }
 
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        public void AddNewLayer(string name, Surface bitmap, bool setAsActive = true)
         {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
-            Layers.Last().LayerBitmap = bitmap;
+            AddNewLayer(name, bitmap.Width, bitmap.Height, setAsActive, bitmap);
         }
 
         public void AddNewLayer(string name, bool setAsActive = true)
         {
-            AddNewLayer(name, 0, 0, setAsActive);
+            AddNewLayer(name, 1, 1, setAsActive);
         }
 
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true, Surface bitmap = null)
         {
             Layer layer;
 
-            Layers.Add(layer = new Layer(name, width, height)
+            if (bitmap != null)
             {
-                MaxHeight = Height,
-                MaxWidth = Width
-            });
+                if (width != bitmap.Width || height != bitmap.Height)
+                    throw new ArgumentException("Inconsistent width and height");
+            }
+            if (width <= 0 || height <= 0)
+                throw new ArgumentException("Dimensions must be greater than 0");
+
+            layer = bitmap == null ? new Layer(name, width, height) : new Layer(name, bitmap);
+            layer.MaxHeight = Height;
+            layer.MaxWidth = Width;
+
+            Layers.Add(layer);
 
             layer.Name = GetLayerSuffix(layer);
 
@@ -479,9 +494,24 @@ namespace PixiEditor.Models.DataHolders
             return layer;
         }
 
-        public Color GetColorAtPoint(int x, int y)
+        public SKColor GetColorAtPoint(int x, int y)
+        {
+            return Renderer.FinalSurface.GetSRGBPixel(x, y);
+        }
+
+        private void DisposeLayerBitmaps()
         {
-            return BitmapUtils.GetColorAtPointCombined(x, y, Layers.ToArray());
+            foreach (var layer in layers)
+            {
+                layer.LayerBitmap.Dispose();
+            }
+
+            referenceLayer?.LayerBitmap.Dispose();
+            previewLayer?.LayerBitmap.Dispose();
+
+            previewLayerRenderer?.Dispose();
+            referenceLayerRenderer?.Dispose();
+            renderer?.Dispose();
         }
 
         private void BuildLayerStructureProcess(object[] parameters)
@@ -522,7 +552,7 @@ namespace PixiEditor.Models.DataHolders
 
             if (Layers.Count == 0)
             {
-                Layer layer = new("Base Layer", 0, 0) { MaxHeight = Height, MaxWidth = Width };
+                Layer layer = new("Base Layer", 1, 1) { MaxHeight = Height, MaxWidth = Width };
                 Layers.Add(layer);
                 undoAction = (Layer[] layers, UndoLayer[] undoData) =>
                 {

+ 138 - 3
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -1,7 +1,9 @@
-using PixiEditor.Helpers.Extensions;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
+using SkiaSharp;
 using System;
 using System.Linq;
 using System.Windows;
@@ -46,6 +48,35 @@ namespace PixiEditor.Models.DataHolders
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
 
+        public void RotateActiveDocument(float degrees)
+        {
+            object[] processArgs = { degrees };
+            object[] reverseProcessArgs = { -degrees };
+
+            RotateDocumentProcess(processArgs);
+
+            UndoManager.AddUndoChange(new Change(
+                RotateDocumentProcess,
+                reverseProcessArgs,
+                RotateDocumentProcess,
+                processArgs,
+                "Rotate layer"));
+        }
+
+        public void FlipActiveDocument(FlipType flip)
+        {
+            object[] processArgs = { flip };
+
+            FlipDocumentProcess(processArgs);
+
+            UndoManager.AddUndoChange(new Change(
+                FlipDocumentProcess,
+                processArgs,
+                FlipDocumentProcess,
+                processArgs,
+                $"Flip layer: {flip}"));
+        }
+
         /// <summary>
         ///     Resizes all document layers using NearestNeighbor interpolation.
         /// </summary>
@@ -68,6 +99,110 @@ namespace PixiEditor.Models.DataHolders
                     "Resize document"));
         }
 
+
+        private void FlipDocumentProcess(object[] processArgs)
+        {
+            FlipType flip = (FlipType)processArgs[0];
+            foreach (var layer in Layers)
+            {
+                using (new SKAutoCanvasRestore(layer.LayerBitmap.SkiaSurface.Canvas, true))
+                {
+                    var copy = layer.LayerBitmap.SkiaSurface.Snapshot();
+
+                    var canvas = layer.LayerBitmap.SkiaSurface.Canvas;
+
+                    layer.ClipCanvas();
+
+                    if (flip == FlipType.Horizontal)
+                    {
+                        canvas.Translate(layer.Width, 0);
+                        canvas.Scale(-1, 1, 0, 0);
+                    }
+                    else
+                    {
+                        canvas.Translate(0, layer.Width);
+                        canvas.Scale(1, -1, 0, 0);
+                    }
+
+                    // Flip offset based on document and layer center point
+                    var documentCenter = new Coordinates(Width / 2, Height / 2);
+                    var layerCenter = new Coordinates(layer.Width / 2, layer.Height / 2);
+
+                    int newOffsetX = layer.OffsetX;
+                    int newOffsetY = layer.OffsetY;
+
+                    if (flip == FlipType.Horizontal)
+                    {
+                        newOffsetX += layerCenter.X;
+                        int diff = documentCenter.X - newOffsetX;
+                        newOffsetX = layer.OffsetX + (diff * 2);
+                    }
+                    else if(flip == FlipType.Vertical)
+                    {
+                        newOffsetY += layerCenter.Y;
+                        int diff = documentCenter.Y - newOffsetY;
+                        newOffsetY = layer.OffsetY + (diff * 2);
+                    }
+
+                    layer.Offset = new Thickness(newOffsetX, newOffsetY, 0, 0);
+
+                    canvas.DrawImage(copy, default(SKPoint));
+                    copy.Dispose();
+                }
+
+                layer.InvokeLayerBitmapChange();
+            }
+        }
+
+        private void RotateDocumentProcess(object[] parameters)
+        {
+            float degrees = (float)parameters[0];
+
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            int biggerMaxSize = Math.Max(Width, Height);
+
+            // TODO: Fix v0.2
+            foreach (var layer in Layers)
+            {
+                using (new SKAutoCanvasRestore(layer.LayerBitmap.SkiaSurface.Canvas, true))
+                {
+                    var copy = layer.LayerBitmap.SkiaSurface.Snapshot();
+
+                    double radians = Math.PI * degrees / 180;
+                    float sine = (float)Math.Abs(Math.Sin(radians));
+                    float cosine = (float)Math.Abs(Math.Cos(radians));
+                    int originalWidth = layer.Width;
+                    int originalHeight = layer.Height;
+                    int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight);
+                    int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth);
+
+                    layer.CreateNewBitmap(rotatedWidth, rotatedHeight);
+
+                    var surface = layer.LayerBitmap.SkiaSurface.Canvas;
+
+                    surface.Translate(rotatedWidth / 2, rotatedHeight / 2);
+                    surface.RotateDegrees((float)degrees);
+                    surface.Translate(-originalWidth / 2, -originalHeight / 2);
+                    surface.DrawImage(copy, default(SKPoint));
+
+                    layer.MaxHeight = oldWidth;
+                    layer.MaxWidth = oldHeight;
+
+                    copy.Dispose();
+                }
+
+                layer.InvokeLayerBitmapChange();
+            }
+
+            Height = oldWidth;
+            Width = oldHeight;
+            DocumentSizeChanged?.Invoke(
+                this,
+                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, Width, Height));
+        }
+
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         {
             Width = (int)args[0];
@@ -112,8 +247,8 @@ namespace PixiEditor.Models.DataHolders
             {
                 float widthRatio = (float)newWidth / Width;
                 float heightRatio = (float)newHeight / Height;
-                int layerWidth = (int)(Layers[i].Width * widthRatio);
-                int layerHeight = (int)(Layers[i].Height * heightRatio);
+                int layerWidth = Math.Max(1, (int)(Layers[i].Width * widthRatio));
+                int layerHeight = Math.Max(1, (int)(Layers[i].Height * heightRatio));
 
                 Layers[i].Resize(layerWidth, layerHeight, newWidth, newHeight);
                 Layers[i].Offset = new Thickness(Math.Floor(Layers[i].OffsetX * widthRatio), Math.Floor(Layers[i].OffsetY * heightRatio), 0, 0);

+ 12 - 2
PixiEditor/Models/DataHolders/Document/Document.Preview.cs

@@ -1,6 +1,7 @@
-using System.Windows.Media.Imaging;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -14,6 +15,7 @@ namespace PixiEditor.Models.DataHolders
         }
 
         private Layer previewLayer;
+        private SingleLayerRenderer previewLayerRenderer;
 
         public Layer PreviewLayer
         {
@@ -21,10 +23,18 @@ namespace PixiEditor.Models.DataHolders
             set
             {
                 previewLayer = value;
+                previewLayerRenderer?.Dispose();
+                previewLayerRenderer = previewLayer == null ? null : new SingleLayerRenderer(previewLayer, Width, Height);
                 RaisePropertyChanged(nameof(PreviewLayer));
+                RaisePropertyChanged(nameof(PreviewLayerRenderer));
             }
         }
 
+        public SingleLayerRenderer PreviewLayerRenderer
+        {
+            get => previewLayerRenderer;
+        }
+
         public void UpdatePreviewImage()
         {
             previewImage = BitmapUtils.GeneratePreviewBitmap(this, 30, 20);
@@ -40,4 +50,4 @@ namespace PixiEditor.Models.DataHolders
             };
         }
     }
-}
+}

+ 31 - 32
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -2,9 +2,11 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers.Utils;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels;
+using SkiaSharp;
 using System;
 using System.Buffers;
 using System.Collections.Generic;
@@ -13,13 +15,11 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
     [DebuggerDisplay("'{Name, nq}' {width}x{height} {Layers.Count} Layer(s)")]
-    public partial class Document : NotifyableObject
+    public partial class Document : NotifyableObject, IDisposable
     {
 
         private ViewModelMain xamlAccesibleViewModel = null;
@@ -33,36 +33,28 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
-        private Layer referenceLayer;
-
-        public Layer ReferenceLayer
-        {
-            get => referenceLayer;
-            set => SetProperty(ref referenceLayer, value);
-        }
-
         public string Name
         {
             get => (string.IsNullOrEmpty(DocumentFilePath) ? "Untitled" : Path.GetFileName(DocumentFilePath))
                 + (!ChangesSaved ? " *" : string.Empty);
         }
 
-        private int width;
+        private int width = 1;
         public int Width
         {
             get => width;
-            set
+            private set
             {
                 width = value;
-                RaisePropertyChanged("Width");
+                RaisePropertyChanged(nameof(Width));
             }
         }
 
-        private int height;
+        private int height = 1;
         public int Height
         {
             get => height;
-            set
+            private set
             {
                 height = value;
                 RaisePropertyChanged("Height");
@@ -108,7 +100,7 @@ namespace PixiEditor.Models.DataHolders
 
         public UndoManager UndoManager { get; set; }
 
-        public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
+        public ObservableCollection<SKColor> Swatches { get; set; } = new ObservableCollection<SKColor>();
 
         public void RaisePropertyChange(string name)
         {
@@ -120,7 +112,7 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         public void ClipCanvas()
         {
-            DoubleCords points = GetEdgePoints(Layers);
+            DoubleCoords points = GetEdgePoints(Layers);
             int smallestX = points.Coords1.X;
             int smallestY = points.Coords1.Y;
             int biggestX = points.Coords2.X;
@@ -159,13 +151,13 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         public void CenterContent()
         {
-            var layersToCenter = Layers.Where(x => x.IsActive && GetFinalLayerIsVisible(x));
+            var layersToCenter = Layers.Where(x => x.IsActive && LayerStructureUtils.GetFinalLayerIsVisible(x, LayerStructure));
             if (!layersToCenter.Any())
             {
                 return;
             }
 
-            DoubleCords points = GetEdgePoints(layersToCenter);
+            DoubleCoords points = GetEdgePoints(layersToCenter);
 
             int smallestX = points.Coords1.X;
             int smallestY = points.Coords1.Y;
@@ -193,10 +185,18 @@ namespace PixiEditor.Models.DataHolders
                     "Center content"));
         }
 
+        public void Dispose()
+        {
+            DisposeLayerBitmaps();
+            UndoManager.Dispose();
+
+            GC.SuppressFinalize(this);
+        }
+
         private void SetAsActiveOnClick(object obj)
         {
             XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
-            XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
+            //XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
             if (XamlAccesibleViewModel.BitmapManager.ActiveDocument != this)
             {
                 XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
@@ -212,12 +212,12 @@ namespace PixiEditor.Models.DataHolders
         {
             if (anchor.HasFlag(AnchorPoint.Center))
             {
-                return Math.Abs((destWidth / 2) - (srcWidth / 2));
+                return (destWidth / 2) - (srcWidth / 2);
             }
 
             if (anchor.HasFlag(AnchorPoint.Right))
             {
-                return Math.Abs(destWidth - srcWidth);
+                return destWidth - srcWidth;
             }
 
             return 0;
@@ -227,29 +227,28 @@ namespace PixiEditor.Models.DataHolders
         {
             if (anchor.HasFlag(AnchorPoint.Middle))
             {
-                return Math.Abs((destHeight / 2) - (srcHeight / 2));
+                return (destHeight / 2) - (srcHeight / 2);
             }
 
             if (anchor.HasFlag(AnchorPoint.Bottom))
             {
-                return Math.Abs(destHeight - srcHeight);
+                return destHeight - srcHeight;
             }
 
             return 0;
         }
 
-        private DoubleCords GetEdgePoints(IEnumerable<Layer> layers)
+        private DoubleCoords GetEdgePoints(IEnumerable<Layer> layers)
         {
             if (Layers.Count == 0)
             {
                 throw new ArgumentException("Not enough layers");
             }
 
-            Layer firstLayer = layers.First();
-            int smallestX = firstLayer.OffsetX;
-            int smallestY = firstLayer.OffsetY;
-            int biggestX = smallestX + firstLayer.Width;
-            int biggestY = smallestY + firstLayer.Height;
+            int smallestX = int.MaxValue;
+            int smallestY = int.MaxValue;
+            int biggestX = int.MinValue;
+            int biggestY = int.MinValue;
 
             foreach (Layer layer in layers)
             {
@@ -275,7 +274,7 @@ namespace PixiEditor.Models.DataHolders
                 }
             }
 
-            return new DoubleCords(
+            return new DoubleCoords(
                 new Coordinates(smallestX, smallestY),
                 new Coordinates(biggestX, biggestY));
         }

+ 0 - 24
PixiEditor/Models/DataHolders/LayerChange.cs

@@ -1,24 +0,0 @@
-using System;
-using PixiEditor.Models.Layers;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public class LayerChange
-    {
-        public LayerChange(BitmapPixelChanges pixelChanges, Guid layerGuid)
-        {
-            PixelChanges = pixelChanges;
-            LayerGuid = layerGuid;
-        }
-
-        public LayerChange(BitmapPixelChanges pixelChanges, Layer layer)
-        {
-            PixelChanges = pixelChanges;
-            LayerGuid = layer.LayerGuid;
-        }
-
-        public BitmapPixelChanges PixelChanges { get; set; }
-
-        public Guid LayerGuid { get; set; }
-    }
-}

+ 15 - 24
PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs

@@ -1,10 +1,11 @@
-using System.Diagnostics;
-using System.IO;
-using System.Windows.Media.Imaging;
-using PixiEditor.Helpers;
-using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Helpers;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.Position;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
+using System.Diagnostics;
+using System.IO;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -49,14 +50,13 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
-        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
         public WriteableBitmap PreviewBitmap
         {
             get
             {
-                if (previewBitmap == null)
+                if (previewBitmap == null && !Corrupt)
                 {
-                    PreviewBitmap = LoadPreviewBitmap();
+                    previewBitmap = LoadPreviewBitmap();
                 }
 
                 return previewBitmap;
@@ -85,21 +85,12 @@ namespace PixiEditor.Models.DataHolders
                     return null;
                 }
 
-                WriteableBitmap writeableBitmap =
-                    BitmapUtils.GeneratePreviewBitmap(serializableDocument.Layers, serializableDocument.Width, serializableDocument.Height, 80, 50);
-
-                const int MaxWidthInPixels = 1080;
-                const int MaxHeightInPixels = 1080;
-                PixiFileMaxSizeChecker pixiFileMaxSizeChecker = new PixiFileMaxSizeChecker()
-                {
-                    MaxAllowedWidthInPixels = MaxWidthInPixels,
-                    MaxAllowedHeightInPixels = MaxHeightInPixels,
-                    MaxAllowedLayerCount = 5,
-                };
+                using Surface surface = Surface.Combine(serializableDocument.Width, serializableDocument.Height,
+                          serializableDocument.Layers
+                              .Where(x => x.Opacity > 0.8)
+                              .Select(x => (x.ToSKImage(), new Coordinates(x.OffsetX, x.OffsetY))));
 
-                return pixiFileMaxSizeChecker.IsFileUnderMaxSize(serializableDocument) ?
-                    writeableBitmap
-                    : writeableBitmap.Resize(width: MaxWidthInPixels, height: MaxHeightInPixels, WriteableBitmapExtensions.Interpolation.Bilinear);
+                return surface.ToWriteableBitmap();
             }
             else if (FileExtension is ".png" or ".jpg" or ".jpeg")
             {
@@ -107,7 +98,7 @@ namespace PixiEditor.Models.DataHolders
 
                 try
                 {
-                    bitmap = Importer.ImportImage(FilePath);
+                    bitmap = Importer.ImportWriteableBitmap(FilePath);
                 }
                 catch
                 {
@@ -130,4 +121,4 @@ namespace PixiEditor.Models.DataHolders
             return null;
         }
     }
-}
+}

+ 55 - 14
PixiEditor/Models/DataHolders/Selection.cs

@@ -1,26 +1,27 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Windows.Media;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using SkiaSharp;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
 
 namespace PixiEditor.Models.DataHolders
 {
     [DebuggerDisplay("{SelectedPoints.Count} selected Pixels")]
     public class Selection : NotifyableObject
     {
-        private readonly Color selectionBlue;
+        private readonly SKColor selectionBlue;
         private Layer selectionLayer;
 
         public Selection(Coordinates[] selectedPoints)
         {
             SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
             SelectionLayer = new Layer("_selectionLayer");
-            selectionBlue = Color.FromArgb(127, 142, 202, 255);
+            selectionBlue = new SKColor(142, 202, 255, 255);
         }
 
         public ObservableCollection<Coordinates> SelectedPoints { get; private set; }
@@ -31,35 +32,75 @@ namespace PixiEditor.Models.DataHolders
             set
             {
                 selectionLayer = value;
-                RaisePropertyChanged("SelectionLayer");
+                RaisePropertyChanged(nameof(SelectionLayer));
             }
         }
 
         public void SetSelection(IEnumerable<Coordinates> selection, SelectionType mode)
         {
-            Color selectionColor = selectionBlue;
+            SKColor selectionColor = selectionBlue;
             switch (mode)
             {
                 case SelectionType.New:
                     SelectedPoints = new ObservableCollection<Coordinates>(selection);
-                    SelectionLayer.Clear();
+                    SelectionLayer.Reset();
                     break;
                 case SelectionType.Add:
                     SelectedPoints = new ObservableCollection<Coordinates>(SelectedPoints.Concat(selection).Distinct());
                     break;
                 case SelectionType.Subtract:
                     SelectedPoints = new ObservableCollection<Coordinates>(SelectedPoints.Except(selection));
-                    selectionColor = System.Windows.Media.Colors.Transparent;
+                    selectionColor = SKColors.Transparent;
                     break;
             }
 
             SelectionLayer.SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, selectionColor));
         }
 
+        public void TranslateSelection(int dX, int dY)
+        {
+            selectionLayer.Offset = new Thickness(selectionLayer.OffsetX + dX, selectionLayer.OffsetY + dY, 0, 0);
+            for (int i = 0; i < SelectedPoints.Count; i++)
+            {
+                SelectedPoints[i] = new Coordinates(SelectedPoints[i].X + dX, SelectedPoints[i].Y + dY);
+            }
+        }
+
+        public void SetSelection(Int32Rect rect, bool isCirclular, SelectionType mode)
+        {
+            using SKPaint paint = new()
+            {
+                Color = selectionBlue,
+                BlendMode = SKBlendMode.Src,
+                Style = SKPaintStyle.StrokeAndFill,
+            };
+            switch (mode)
+            {
+                case SelectionType.New:
+                    SelectionLayer.Reset();
+                    break;
+                case SelectionType.Subtract:
+                    paint.Color = SKColors.Transparent;
+                    break;
+            }
+
+            SelectionLayer.DynamicResizeAbsolute(new Int32Rect(rect.X, rect.Y, rect.Width, rect.Height));
+            if (isCirclular)
+            {
+                float cx = rect.X + rect.Width / 2f;
+                float cy = rect.Y + rect.Height / 2f;
+                SelectionLayer.LayerBitmap.SkiaSurface.Canvas.DrawOval(cx, cy, rect.Width / 2f, rect.Height / 2f, paint);
+            }
+            else
+            {
+                SelectionLayer.LayerBitmap.SkiaSurface.Canvas.DrawRect(rect.X, rect.Y, rect.Width, rect.Height, paint);
+            }
+        }
+
         public void Clear()
         {
-            SelectionLayer.Clear();
+            SelectionLayer.Reset();
             SelectedPoints.Clear();
         }
     }
-}
+}

+ 279 - 0
PixiEditor/Models/DataHolders/Surface.cs

@@ -0,0 +1,279 @@
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Position;
+using SkiaSharp;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class Surface : IDisposable
+    {
+        public static SKPaint ReplacingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.Src };
+
+        private static readonly SKPaint nearestNeighborReplacingPaint = new SKPaint() { BlendMode = SKBlendMode.Src, FilterQuality = SKFilterQuality.None };
+
+        public SKSurface SkiaSurface { get; private set; }
+        public int Width { get; }
+        public int Height { get; }
+
+        private SKPaint drawingPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+        private IntPtr surfaceBuffer;
+
+        public Surface(int w, int h)
+        {
+            if (w <= 0 || h <= 0)
+                throw new ArgumentException("Surface dimensions must be non-zero");
+            InitSurface(w, h);
+            Width = w;
+            Height = h;
+        }
+
+        public Surface(Surface original)
+        {
+            Width = original.Width;
+            Height = original.Height;
+            InitSurface(Width, Height);
+            original.SkiaSurface.Draw(SkiaSurface.Canvas, 0, 0, ReplacingPaint);
+        }
+
+        public Surface(int w, int h, byte[] pbgra32Bytes)
+        {
+            if (w <= 0 || h <= 0)
+                throw new ArgumentException("Surface dimensions must be non-zero");
+            Width = w;
+            Height = h;
+            InitSurface(w, h);
+            DrawBytes(w, h, pbgra32Bytes, SKColorType.Bgra8888, SKAlphaType.Premul);
+        }
+
+        public Surface(BitmapSource original)
+        {
+            SKColorType color = original.Format.ToSkia(out SKAlphaType alpha);
+            if (original.PixelWidth <= 0 || original.PixelHeight <= 0)
+                throw new ArgumentException("Surface dimensions must be non-zero");
+
+            byte[] pixels = new byte[original.PixelWidth * original.PixelHeight * 4];
+            original.CopyPixels(pixels, original.PixelWidth * 4, 0);
+
+            Width = original.PixelWidth;
+            Height = original.PixelHeight;
+            InitSurface(Width, Height);
+            DrawBytes(Width, Height, pixels, color, alpha);
+        }
+
+        public Surface(SKImage image)
+        {
+            Width = image.Width;
+            Height = image.Height;
+            InitSurface(Width, Height);
+            SkiaSurface.Canvas.DrawImage(image, 0, 0);
+        }
+
+        /// <summary>
+        /// Combines the <paramref name="images"/> into a <see cref="Surface"/>
+        /// </summary>
+        /// <param name="width">The width of the <see cref="Surface"/></param>
+        /// <param name="height">The height of the <see cref="Surface"/></param>
+        /// <returns>A surface that has the <paramref name="images"/> drawn on it</returns>
+        public static Surface Combine(int width, int height, IEnumerable<(SKImage image, Coordinates offset)> images)
+        {
+            Surface surface = new Surface(width, height);
+
+            foreach (var image in images)
+            {
+                surface.SkiaSurface.Canvas.DrawImage(image.image, (SKPoint)image.offset);
+            }
+
+            return surface;
+        }
+
+        public Surface ResizeNearestNeighbor(int newW, int newH)
+        {
+            SKImage image = SkiaSurface.Snapshot();
+            Surface newSurface = new(newW, newH);
+            newSurface.SkiaSurface.Canvas.DrawImage(image, new SKRect(0, 0, newW, newH), nearestNeighborReplacingPaint);
+            return newSurface;
+        }
+
+        public Surface Crop(int x, int y, int width, int height)
+        {
+            Surface result = new Surface(width, height);
+            SkiaSurface.Draw(result.SkiaSurface.Canvas, x, y, ReplacingPaint);
+            return result;
+        }
+
+        public unsafe SKColor GetSRGBPixel(int x, int y)
+        {
+            Half* ptr = (Half*)(surfaceBuffer + (x + y * Width) * 8);
+            SKColor color = (SKColor)new SKColorF((float)ptr[0], (float)ptr[1], (float)ptr[2], (float)ptr[3]);
+            return SKPMColor.UnPreMultiply(new SKPMColor((uint)color));
+        }
+
+        public void SetSRGBPixel(int x, int y, SKColor color)
+        {
+            // It's possible that this function can be sped up by writing into surfaceBuffer, not sure if skia will like it though
+            drawingPaint.Color = color;
+            SkiaSurface.Canvas.DrawPoint(x, y, drawingPaint);
+        }
+
+        public unsafe byte[] ToByteArray(SKColorType colorType = SKColorType.Bgra8888, SKAlphaType alphaType = SKAlphaType.Premul)
+        {
+            var imageInfo = new SKImageInfo(Width, Height, colorType, alphaType, SKColorSpace.CreateSrgb());
+
+            byte[] buffer = new byte[Width * Height * imageInfo.BytesPerPixel];
+            fixed (void* pointer = buffer)
+            {
+                if (!SkiaSurface.ReadPixels(imageInfo, new IntPtr(pointer), imageInfo.RowBytes, 0, 0))
+                {
+                    throw new InvalidOperationException("Could not read surface into buffer");
+                }
+            }
+
+            return buffer;
+        }
+
+        public WriteableBitmap ToWriteableBitmap()
+        {
+            WriteableBitmap result = new WriteableBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32, null);
+            result.Lock();
+            var dirty = new Int32Rect(0, 0, Width, Height);
+            result.WritePixels(dirty, ToByteArray(), Width * 4, 0);
+            result.AddDirtyRect(dirty);
+            result.Unlock();
+            return result;
+        }
+
+        public void Dispose()
+        {
+            SkiaSurface.Dispose();
+            drawingPaint.Dispose();
+            Marshal.FreeHGlobal(surfaceBuffer);
+            GC.SuppressFinalize(this);
+        }
+
+        ~Surface()
+        {
+            Marshal.FreeHGlobal(surfaceBuffer);
+        }
+
+        private static SKSurface CreateSurface(int w, int h, IntPtr buffer)
+        {
+            var surface = SKSurface.Create(new SKImageInfo(w, h, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()), buffer);
+            if (surface == null)
+                throw new Exception("Could not create surface");
+            return surface;
+        }
+        private static SKSurface CreateSurface(int w, int h)
+        {
+            var surface = SKSurface.Create(new SKImageInfo(w, h, SKColorType.RgbaF16, SKAlphaType.Premul, SKColorSpace.CreateSrgb()));
+            if (surface == null)
+                throw new Exception("Could not create surface");
+            return surface;
+        }
+
+        private unsafe void InitSurface(int w, int h)
+        {
+            int byteC = w * h * 8;
+            surfaceBuffer = Marshal.AllocHGlobal(byteC);
+            Unsafe.InitBlockUnaligned((byte*)surfaceBuffer, 0, (uint)byteC);
+            SkiaSurface = CreateSurface(w, h, surfaceBuffer);
+        }
+
+        private unsafe void DrawBytes(int w, int h, byte[] bytes, SKColorType colorType, SKAlphaType alphaType)
+        {
+            SKImageInfo info = new SKImageInfo(w, h, colorType, alphaType);
+
+            fixed (void* pointer = bytes)
+            {
+                using SKPixmap map = new(info, new IntPtr(pointer));
+                using SKSurface surface = SKSurface.Create(map);
+                surface.Draw(SkiaSurface.Canvas, 0, 0, ReplacingPaint);
+            }
+        }
+
+#if DEBUG
+        // Used to iterate the surface's pixels during development
+
+        [Obsolete("Only meant for use in a debugger like Visual Studio", true)]
+        private SurfaceDebugger Debugger => new SurfaceDebugger(this);
+
+        [Obsolete("Only meant for use in a debugger like Visual Studio", true)]
+        private class SurfaceDebugger : IEnumerable
+        {
+            private readonly Surface _surface;
+
+            public SurfaceDebugger(Surface surface)
+            {
+                _surface = surface;
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                var pixmap = _surface.SkiaSurface.PeekPixels();
+
+                for (int y = 0; y < pixmap.Width; y++)
+                {
+                    yield return new DebugPixel(y);
+
+                    for (int x = 0; x < pixmap.Height; x++)
+                    {
+                        yield return new DebugPixel(x, y, pixmap.GetPixelColor(x, y).ToString());
+                    }
+                }
+            }
+
+            [DebuggerDisplay("{DebuggerDisplay,nq}")]
+            private struct DebugPixel
+            {
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private string DebuggerDisplay
+                {
+                    get
+                    {
+                        if (isPixel)
+                        {
+                            return $"X: {x}; Y: {y} - {hex}";
+                        }
+                        else
+                        {
+                            return $"|- Y: {y} -|";
+                        }
+                    }
+                }
+
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly int x;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly int y;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly string hex;
+                [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+                private readonly bool isPixel;
+
+                public DebugPixel(int y)
+                {
+                    x = 0;
+                    this.y = y;
+                    hex = null;
+                    isPixel = false;
+                }
+
+                public DebugPixel(int x, int y, string hex)
+                {
+                    this.x = x;
+                    this.y = y;
+                    this.hex = hex;
+                    isPixel = true;
+                }
+            }
+        }
+#endif
+    }
+}

+ 8 - 0
PixiEditor/Models/Enums/FlipType.cs

@@ -0,0 +1,8 @@
+namespace PixiEditor.Models.Enums
+{
+    public enum FlipType
+    {
+        Horizontal,
+        Vertical
+    }
+}

+ 32 - 7
PixiEditor/Models/IO/Exporter.cs

@@ -1,11 +1,14 @@
-using System;
-using System.IO;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
+using Microsoft.Win32;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
+using SkiaSharp;
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.IO
 {
@@ -41,7 +44,7 @@ namespace PixiEditor.Models.IO
         /// <returns>Path.</returns>
         public static string SaveAsEditableFile(Document document, string path)
         {
-            Parser.PixiParser.Serialize(document.ToSerializable(), path);
+            Parser.PixiParser.Serialize(ParserHelpers.ToSerializable(document), path);
             return path;
         }
 
@@ -68,6 +71,28 @@ namespace PixiEditor.Models.IO
             }
         }
 
+        public static void SaveAsGZippedBytes(string path, Surface surface)
+        {
+            var imageInfo = new SKImageInfo(surface.Width, surface.Height, SKColorType.RgbaF16);
+            var unmanagedBuffer = Marshal.AllocHGlobal(surface.Width * surface.Height * 8);
+            //+8 bytes for width and height
+            var bytes = new byte[surface.Width * surface.Height * 8 + 8];
+            try
+            {
+                surface.SkiaSurface.ReadPixels(imageInfo, unmanagedBuffer, surface.Width * 8, 0, 0);
+                Marshal.Copy(unmanagedBuffer, bytes, 8, surface.Width * surface.Height * 8);
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(unmanagedBuffer);
+            }
+            BitConverter.GetBytes((int)surface.Width).CopyTo(bytes, 0);
+            BitConverter.GetBytes((int)surface.Height).CopyTo(bytes, 4);
+            using FileStream outputStream = new(path, FileMode.Create);
+            using GZipStream compressedStream = new GZipStream(outputStream, CompressionLevel.Fastest);
+            compressedStream.Write(bytes);
+        }
+
         /// <summary>
         ///     Saves image to PNG file.
         /// </summary>
@@ -93,4 +118,4 @@ namespace PixiEditor.Models.IO
             }
         }
     }
-}
+}

+ 53 - 11
PixiEditor/Models/IO/Importer.cs

@@ -1,12 +1,14 @@
-using System;
-using System.IO;
-using System.Runtime.Serialization;
-using System.Windows.Media.Imaging;
-using PixiEditor.Exceptions;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Parser;
+using SkiaSharp;
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.IO
 {
@@ -19,12 +21,14 @@ namespace PixiEditor.Models.IO
         /// <param name="width">New width of image.</param>
         /// <param name="height">New height of image.</param>
         /// <returns>WriteableBitmap of imported image.</returns>
-        public static WriteableBitmap ImportImage(string path, int width, int height)
+        public static Surface ImportImage(string path, int width, int height)
         {
-            WriteableBitmap wbmp = ImportImage(path);
-            if (wbmp.PixelWidth != width || wbmp.PixelHeight != height)
+            Surface wbmp = ImportSurface(path);
+            if (wbmp.Width != width || wbmp.Height != height)
             {
-                return wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+                var resized = wbmp.ResizeNearestNeighbor(width, height);
+                wbmp.Dispose();
+                return resized;
             }
 
             return wbmp;
@@ -34,7 +38,17 @@ namespace PixiEditor.Models.IO
         ///     Imports image from path and resizes it to given dimensions.
         /// </summary>
         /// <param name="path">Path of image.</param>
-        public static WriteableBitmap ImportImage(string path)
+        public static Surface ImportSurface(string path)
+        {
+            using var image = SKImage.FromEncodedData(path);
+            if (image == null)
+                throw new CorruptedFileException();
+            Surface surface = new Surface(image.Width, image.Height);
+            surface.SkiaSurface.Canvas.DrawImage(image, new SKPoint(0, 0));
+            return surface;
+        }
+
+        public static WriteableBitmap ImportWriteableBitmap(string path)
         {
             try
             {
@@ -76,5 +90,33 @@ namespace PixiEditor.Models.IO
             path = path.ToLower();
             return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
         }
+
+        public static Surface LoadFromGZippedBytes(string path)
+        {
+            using FileStream compressedData = new(path, FileMode.Open);
+            using GZipStream uncompressedData = new(compressedData, CompressionMode.Decompress);
+            using MemoryStream resultBytes = new();
+            uncompressedData.CopyTo(resultBytes);
+
+            byte[] bytes = resultBytes.ToArray();
+            int width = BitConverter.ToInt32(bytes, 0);
+            int height = BitConverter.ToInt32(bytes, 4);
+
+            SKImageInfo info = new SKImageInfo(width, height, SKColorType.RgbaF16);
+            var ptr = Marshal.AllocHGlobal(bytes.Length - 8);
+            try
+            {
+                Marshal.Copy(bytes, 8, ptr, bytes.Length - 8);
+                SKPixmap map = new(info, ptr);
+                SKSurface surface = SKSurface.Create(map);
+                var finalSurface = new Surface(width, height);
+                surface.Draw(finalSurface.SkiaSurface.Canvas, 0, 0, Surface.ReplacingPaint);
+                return finalSurface;
+            }
+            finally
+            {
+                Marshal.FreeHGlobal(ptr);
+            }
+        }
     }
-}
+}

+ 52 - 102
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -3,10 +3,11 @@ using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers.Utils;
 using PixiEditor.Models.Position;
 using PixiEditor.Parser;
+using PixiEditor.Parser.Skia;
+using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
@@ -15,78 +16,37 @@ namespace PixiEditor.Models.ImageManipulation
 {
     public static class BitmapUtils
     {
-        /// <summary>
-        ///     Converts pixel bytes to WriteableBitmap.
-        /// </summary>
-        /// <param name="currentBitmapWidth">Width of bitmap.</param>
-        /// <param name="currentBitmapHeight">Height of bitmap.</param>
-        /// <param name="byteArray">Bitmap byte array.</param>
-        /// <returns>WriteableBitmap.</returns>
-        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
-        {
-            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            if (byteArray != null)
-            {
-                bitmap.FromByteArray(byteArray);
-            }
-
-            return bitmap;
-        }
-
-        /// <summary>
-        ///     Converts layers bitmaps into one bitmap.
-        /// </summary>
-        /// <param name="width">Width of final bitmap.</param>
-        /// <param name="height">Height of final bitmap.</param>.
-        /// <param name="layers">Layers to combine.</param>
-        /// <returns>WriteableBitmap of layered bitmaps.</returns>
-        public static WriteableBitmap CombineLayers(int width, int height, IEnumerable<Layer> layers, LayerStructure structure = null)
+        public static Surface CombineLayers(Int32Rect portion, IEnumerable<Layer> layers,  LayerStructure structure = null)
         {
-            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
+            Surface finalSurface = new(portion.Width, portion.Height);
+            using SKPaint paint = new();
 
-            using (finalBitmap.GetBitmapContext())
+            for (int i = 0; i < layers.Count(); i++)
             {
-                for (int i = 0; i < layers.Count(); i++)
+                Layer layer = layers.ElementAt(i);
+                if (structure != null && !LayerStructureUtils.GetFinalLayerIsVisible(layer, structure))
+                    continue;
+                float layerOpacity = structure == null ? layer.Opacity : LayerStructureUtils.GetFinalLayerOpacity(layer, structure);
+                paint.Color = new(255, 255, 255, (byte)(layerOpacity * 255));
+
+                if (layer.OffsetX < 0 || layer.OffsetY < 0 ||
+                    layer.Width + layer.OffsetX > layer.MaxWidth ||
+                    layer.Height + layer.OffsetY > layer.MaxHeight)
                 {
-                    Layer layer = layers.ElementAt(i);
-                    float layerOpacity = structure == null ? layer.Opacity : LayerStructureUtils.GetFinalLayerOpacity(layer, structure);
-
-                    if (layer.OffsetX < 0 || layer.OffsetY < 0 ||
-                        layer.Width + layer.OffsetX > layer.MaxWidth ||
-                        layer.Height + layer.OffsetY > layer.MaxHeight)
-                    {
-                        throw new InvalidOperationException("Layers must not extend beyond canvas borders");
-                    }
-
-                    for (int y = 0; y < layer.Height; y++)
-                    {
-                        for (int x = 0; x < layer.Width; x++)
-                        {
-                            Color previousColor = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
-                            Color color = layer.GetPixel(x, y);
-
-                            finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, BlendColor(previousColor, color, layerOpacity));
-                        }
-                    }
+                    throw new InvalidOperationException("Layers must not extend beyond canvas borders");
                 }
-            }
-
-            return finalBitmap;
-        }
-
-        public static Color GetColorAtPointCombined(int x, int y, params Layer[] layers)
-        {
-            Color prevColor = Color.FromArgb(0, 0, 0, 0);
-
-            for (int i = 0; i < layers.Length; i++)
-            {
-                Color color = layers[i].GetPixelWithOffset(x, y);
-                float layerOpacity = layers[i].Opacity;
 
-                prevColor = BlendColor(prevColor, color, layerOpacity);
+                using SKImage snapshot = layer.LayerBitmap.SkiaSurface.Snapshot();
+                int x = portion.X - layer.OffsetX;
+                int y = portion.Y - layer.OffsetY;
+                finalSurface.SkiaSurface.Canvas.DrawImage(
+                    snapshot,
+                    new SKRect(x, y, portion.Width + x, portion.Height + y),
+                    new SKRect(0, 0, portion.Width, portion.Height),
+                    paint);
             }
 
-            return prevColor;
+            return finalSurface;
         }
 
         /// <summary>
@@ -126,10 +86,11 @@ namespace PixiEditor.Models.ImageManipulation
 
         public static WriteableBitmap GeneratePreviewBitmap(IEnumerable<SerializableLayer> layers, int width, int height, int maxPreviewWidth, int maxPreviewHeight)
         {
-            var opacityLayers = layers.Where(x => x.IsVisible && x.Opacity > 0.8f);
+            var opacityLayers = layers.Where(x => x.IsVisible && x.Opacity > 0.8f
+            && x.Height > 0 && x.Width > 0);
 
             return GeneratePreviewBitmap(
-                opacityLayers.Select(x => BytesToWriteableBitmap(x.Width, x.Height, x.BitmapBytes)),
+                opacityLayers.Select(x => new Surface(x.ToSKImage())),
                 opacityLayers.Select(x => x.OffsetX),
                 opacityLayers.Select(x => x.OffsetY),
                 width,
@@ -138,62 +99,48 @@ namespace PixiEditor.Models.ImageManipulation
                 maxPreviewHeight);
         }
 
-        public static Dictionary<Guid, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
+        public static Dictionary<Guid, SKColor[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
         {
-            Dictionary<Guid, Color[]> result = new();
+            Dictionary<Guid, SKColor[]> result = new();
 
             foreach (Layer layer in layers)
             {
-                Color[] pixels = new Color[selection.Length];
+                SKColor[] pixels = new SKColor[selection.Length];
 
-                using (layer.LayerBitmap.GetBitmapContext())
+                for (int j = 0; j < pixels.Length; j++)
                 {
-                    for (int j = 0; j < pixels.Length; j++)
+                    Coordinates position = layer.GetRelativePosition(selection[j]);
+                    if (position.X < 0 || position.X > layer.Width - 1 || position.Y < 0 ||
+                        position.Y > layer.Height - 1)
                     {
-                        Coordinates position = layer.GetRelativePosition(selection[j]);
-                        if (position.X < 0 || position.X > layer.Width - 1 || position.Y < 0 ||
-                            position.Y > layer.Height - 1)
-                        {
-                            continue;
-                        }
-
-                        pixels[j] = layer.GetPixel(position.X, position.Y);
+                        continue;
                     }
-                }
 
+                    var cl = layer.GetPixel(position.X, position.Y);
+                    pixels[j] = cl;
+                }
                 result[layer.LayerGuid] = pixels;
             }
 
             return result;
         }
 
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static Color BlendColor(Color previousColor, Color color, float opacity)
+        public static SKColor BlendColors(SKColor bottomColor, SKColor topColor)
         {
-            if ((color.A < 255 && color.A > 0) || (opacity < 1f && opacity > 0 && color.A > 0))
-            {
-                byte pixelA = (byte)(color.A * opacity);
-                byte r = (byte)((color.R * pixelA / 255) + (previousColor.R * previousColor.A * (255 - pixelA) / (255 * 255)));
-                byte g = (byte)((color.G * pixelA / 255) + (previousColor.G * previousColor.A * (255 - pixelA) / (255 * 255)));
-                byte b = (byte)((color.B * pixelA / 255) + (previousColor.B * previousColor.A * (255 - pixelA) / (255 * 255)));
-                byte a = (byte)(pixelA + (previousColor.A * (255 - pixelA) / 255));
-                color = Color.FromArgb(a, r, g, b);
-            }
-            else
-            {
-                color = Color.FromArgb(color.A, color.R, color.G, color.B);
-            }
-
-            if (color.A > 0)
+            if ((topColor.Alpha < 255 && topColor.Alpha > 0))
             {
-                return color;
+                byte r = (byte)((topColor.Red * topColor.Alpha / 255) + (bottomColor.Red * bottomColor.Alpha * (255 - topColor.Alpha) / (255 * 255)));
+                byte g = (byte)((topColor.Green * topColor.Alpha / 255) + (bottomColor.Green * bottomColor.Alpha * (255 - topColor.Alpha) / (255 * 255)));
+                byte b = (byte)((topColor.Blue * topColor.Alpha / 255) + (bottomColor.Blue * bottomColor.Alpha * (255 - topColor.Alpha) / (255 * 255)));
+                byte a = (byte)(topColor.Alpha + (bottomColor.Alpha * (255 - topColor.Alpha) / 255));
+                return new SKColor(r, g, b, a);
             }
 
-            return previousColor;
+            return topColor.Alpha == 255 ? topColor : bottomColor;
         }
 
         private static WriteableBitmap GeneratePreviewBitmap(
-            IEnumerable<WriteableBitmap> layerBitmaps,
+            IEnumerable<Surface> layerBitmaps,
             IEnumerable<int> offsetsX,
             IEnumerable<int> offsetsY,
             int width,
@@ -208,6 +155,9 @@ namespace PixiEditor.Models.ImageManipulation
                 throw new ArgumentException("There were not the same amount of bitmaps and offsets", nameof(layerBitmaps));
             }
 
+            using Surface previewSurface = new Surface(maxPreviewWidth, maxPreviewHeight);
+            return previewSurface.ToWriteableBitmap();
+            /*
             WriteableBitmap previewBitmap = BitmapFactory.New(width, height);
 
             var layerBitmapsEnumerator = layerBitmaps.GetEnumerator();
@@ -231,7 +181,7 @@ namespace PixiEditor.Models.ImageManipulation
 
             int newWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
             int newHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
-            return previewBitmap.Resize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            return previewBitmap.Redesize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);*/
         }
     }
 }

+ 257 - 0
PixiEditor/Models/ImageManipulation/ToolCalculator.cs

@@ -0,0 +1,257 @@
+using PixiEditor.Helpers;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.Tools.Tools;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public static class ToolCalculator
+    {
+        /// <summary>
+        /// This function calculates fill and outputs it into Coordinates list.
+        /// </summary>
+        /// <remarks>All coordinates are calculated without layer offset.
+        /// If you want to take consideration offset, just do it in CalculateBresenhamLine in PerformLinearFill function.</remarks>
+        /// <param name="layer">Layer to calculate fill from.</param>
+        /// <param name="startingCoords">Starting fill coordinates</param>
+        /// <param name="maxWidth">Maximum fill width</param>
+        /// <param name="maxHeight">Maximum fill height</param>
+        /// <param name="newColor">Replacement color to stop on</param>
+        /// <param name="output">List with output coordinates.</param>
+        public static void GetLinearFillAbsolute(Layer layer, Coordinates startingCoords, int maxWidth, int maxHeight, SKColor newColor, List<Coordinates> output)
+        {
+            Queue<FloodFillRange> floodFillQueue = new Queue<FloodFillRange>();
+            SKColor colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
+            if ((colorToReplace.Alpha == 0 && newColor.Alpha == 0) ||
+                colorToReplace == newColor)
+                return;
+
+            int width = maxWidth;
+            int height = maxHeight;
+            if (startingCoords.X < 0 || startingCoords.Y < 0 || startingCoords.X >= width || startingCoords.Y >= height)
+                return;
+            var visited = new bool[width * height];
+
+            Int32Rect dirtyRect = new Int32Rect(startingCoords.X, startingCoords.Y, 1, 1);
+
+            PerformLinearFill(layer, floodFillQueue, startingCoords, width, colorToReplace, ref dirtyRect, visited, output);
+            PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, visited, output);
+        }
+
+        public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
+            List<Coordinates> output)
+        {
+            DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
+            
+            EllipseGenerator.GenerateEllipseFromRect(fixedCoordinates, output);
+            if (fill)
+            {
+                CalculateFillForEllipse(output);
+            }
+        }
+
+        public static void GenerateRectangleNonAlloc(
+            Coordinates start,
+            Coordinates end, bool fill, int thickness, List<Coordinates> output)
+        {
+            DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
+            CalculateRectanglePoints(fixedCoordinates, output);
+
+            for (int i = 1; i < (int)Math.Floor(thickness / 2f) + 1; i++)
+            {
+                CalculateRectanglePoints(
+                    new DoubleCoords(
+                    new Coordinates(fixedCoordinates.Coords1.X - i, fixedCoordinates.Coords1.Y - i),
+                    new Coordinates(fixedCoordinates.Coords2.X + i, fixedCoordinates.Coords2.Y + i)), output);
+            }
+
+            for (int i = 1; i < (int)Math.Ceiling(thickness / 2f); i++)
+            {
+                CalculateRectanglePoints(
+                    new DoubleCoords(
+                    new Coordinates(fixedCoordinates.Coords1.X + i, fixedCoordinates.Coords1.Y + i),
+                    new Coordinates(fixedCoordinates.Coords2.X - i, fixedCoordinates.Coords2.Y - i)), output);
+            }
+
+            if (fill)
+            {
+                CalculateRectangleFillNonAlloc(start, end, thickness, output);
+            }
+        }
+
+        public static DoubleCoords CalculateCoordinatesForShapeRotation(
+            Coordinates startingCords,
+            Coordinates secondCoordinates)
+        {
+            Coordinates currentCoordinates = secondCoordinates;
+
+            if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
+            {
+                return new DoubleCoords(
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y),
+                    new Coordinates(startingCords.X, startingCords.Y));
+            }
+
+            if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
+            {
+                return new DoubleCoords(
+                    new Coordinates(startingCords.X, startingCords.Y),
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y));
+            }
+
+            if (startingCords.Y > currentCoordinates.Y)
+            {
+                return new DoubleCoords(
+                    new Coordinates(startingCords.X, currentCoordinates.Y),
+                    new Coordinates(currentCoordinates.X, startingCords.Y));
+            }
+
+            if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
+            {
+                return new DoubleCoords(
+                    new Coordinates(currentCoordinates.X, startingCords.Y),
+                    new Coordinates(startingCords.X, currentCoordinates.Y));
+            }
+
+            return new DoubleCoords(startingCords, secondCoordinates);
+        }
+
+        private static void PerformLinearFill(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            Coordinates coords, int width, SKColor colorToReplace, ref Int32Rect dirtyRect, bool[] visited, List<Coordinates> output)
+        {
+            // Find the Left Edge of the Color Area
+            int fillXLeft = coords.X;
+            while (true)
+            {
+                // Indicate that this pixel has been checked
+                int pixelIndex = (coords.Y * width) + fillXLeft;
+                visited[pixelIndex] = true;
+
+                // Move one pixel to the left
+                fillXLeft--;
+                // Exit the loop if we're at edge of the bitmap or the color area
+                if (fillXLeft < 0 || visited[pixelIndex - 1] || layer.GetPixelWithOffset(fillXLeft, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelLeft = fillXLeft + 1;
+
+            // Find the Right Edge of the Color Area
+            int fillXRight = coords.X;
+            while (true)
+            {
+                int pixelIndex = (coords.Y * width) + fillXRight;
+                visited[pixelIndex] = true;
+
+                fillXRight++;
+                if (fillXRight >= width || visited[pixelIndex + 1] || layer.GetPixelWithOffset(fillXRight, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelRight = fillXRight - 1;
+
+            int relativeY = coords.Y;
+            LineTool.CalculateBresenhamLine(new Coordinates(lastCheckedPixelLeft, relativeY), new Coordinates(lastCheckedPixelRight, relativeY), output);
+            dirtyRect = dirtyRect.Expand(new Int32Rect(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));
+
+            FloodFillRange range = new FloodFillRange(lastCheckedPixelLeft, lastCheckedPixelRight, coords.Y);
+            floodFillQueue.Enqueue(range);
+        }
+
+        private static void PerformFloodFIll(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            SKColor colorToReplace, ref Int32Rect dirtyRect, int width, int height, bool[] pixelsVisited, List<Coordinates> output)
+        {
+            while (floodFillQueue.Count > 0)
+            {
+                FloodFillRange range = floodFillQueue.Dequeue();
+
+                //START THE LOOP UPWARDS AND DOWNWARDS
+                int upY = range.Y - 1; //so we can pass the y coord by ref
+                int downY = range.Y + 1;
+                int downPixelxIndex = (width * (range.Y + 1)) + range.StartX;
+                int upPixelIndex = (width * (range.Y - 1)) + range.StartX;
+                for (int i = range.StartX; i <= range.EndX; i++)
+                {
+                    //START LOOP UPWARDS
+                    //if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
+                    if (range.Y > 0 && (!pixelsVisited[upPixelIndex]) && layer.GetPixelWithOffset(i, upY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, upY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
+                    //START LOOP DOWNWARDS
+                    if (range.Y < (height - 1) && (!pixelsVisited[downPixelxIndex]) && layer.GetPixelWithOffset(i, downY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, downY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
+                    downPixelxIndex++;
+                    upPixelIndex++;
+                }
+            }
+        }
+
+        private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
+        {
+            if (!outlineCoordinates.Any())
+                return;
+
+            var lines = EllipseGenerator.SplitEllipseIntoLines(outlineCoordinates);
+            foreach (var line in lines)
+            {
+                for (int i = line.Coords1.X; i <= line.Coords2.X; i++)
+                {
+                    outlineCoordinates.Add(new Coordinates(i, line.Coords1.Y));
+                }
+            }
+        }
+
+        private static void CalculateRectangleFillNonAlloc(Coordinates start, Coordinates end, int thickness, List<Coordinates> output)
+        {
+            int offset = (int)Math.Ceiling(thickness / 2f);
+            DoubleCoords fixedCords = CalculateCoordinatesForShapeRotation(start, end);
+
+            DoubleCoords innerCords = new DoubleCoords
+            {
+                Coords1 = new Coordinates(fixedCords.Coords1.X + offset, fixedCords.Coords1.Y + offset),
+                Coords2 = new Coordinates(fixedCords.Coords2.X - (offset - 1), fixedCords.Coords2.Y - (offset - 1))
+            };
+
+            int height = innerCords.Coords2.Y - innerCords.Coords1.Y;
+            int width = innerCords.Coords2.X - innerCords.Coords1.X;
+
+            if (height < 1 || width < 1)
+            {
+                return;
+            }
+
+            int i = 0;
+            for (int y = 0; y < height; y++)
+            {
+                for (int x = 0; x < width; x++)
+                {
+                    output.Add(new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y));
+                    i++;
+                }
+            }
+        }
+
+        private static void CalculateRectanglePoints(DoubleCoords coordinates, List<Coordinates> output)
+        {
+            for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
+            {
+                output.Add(new Coordinates(i, coordinates.Coords1.Y));
+                output.Add(new Coordinates(i, coordinates.Coords2.Y));
+            }
+
+            for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
+            {
+                output.Add(new Coordinates(coordinates.Coords1.X, i));
+                output.Add(new Coordinates(coordinates.Coords2.X, i));
+            }
+        }
+    }
+}

+ 220 - 116
PixiEditor/Models/Layers/Layer.cs

@@ -1,28 +1,27 @@
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels;
+using SkiaSharp;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
 using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.Layers
 {
     [DebuggerDisplay("'{name,nq}' {width}x{height}")]
     public class Layer : BasicLayer
     {
-        private const int SizeOfArgb = 4;
         private bool clipRequested;
 
         private bool isActive;
 
         private bool isRenaming;
         private bool isVisible = true;
-        private WriteableBitmap layerBitmap;
+        private Surface layerBitmap;
 
         private string name;
 
@@ -35,31 +34,33 @@ namespace PixiEditor.Models.Layers
         public Layer(string name)
         {
             Name = name;
-            LayerBitmap = BitmapFactory.New(0, 0);
-            Width = 0;
-            Height = 0;
+            LayerBitmap = new Surface(1, 1);
+            IsReset = true;
+            Width = 1;
+            Height = 1;
             LayerGuid = Guid.NewGuid();
         }
 
         public Layer(string name, int width, int height)
         {
             Name = name;
-            LayerBitmap = BitmapFactory.New(width, height);
+            LayerBitmap = new Surface(width, height);
+            IsReset = true;
             Width = width;
             Height = height;
             LayerGuid = Guid.NewGuid();
         }
 
-        public Layer(string name, WriteableBitmap layerBitmap)
+        public Layer(string name, Surface layerBitmap)
         {
             Name = name;
             LayerBitmap = layerBitmap;
-            Width = layerBitmap.PixelWidth;
-            Height = layerBitmap.PixelHeight;
+            Width = layerBitmap.Width;
+            Height = layerBitmap.Height;
             LayerGuid = Guid.NewGuid();
         }
 
-        public Dictionary<Coordinates, Color> LastRelativeCoordinates { get; set; }
+        public Dictionary<Coordinates, SKColor> LastRelativeCoordinates { get; set; }
 
         public string LayerHighlightColor
         {
@@ -96,11 +97,11 @@ namespace PixiEditor.Models.Layers
             get => isVisible;
             set
             {
-                if (SetProperty(ref isVisible, value))
-                {
-                    RaisePropertyChanged(nameof(IsVisibleUndoTriggerable));
-                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
-                }
+                isVisible = value;
+                RaisePropertyChanged(nameof(IsVisibleUndoTriggerable));
+                RaisePropertyChanged(nameof(IsVisible));
+                ViewModelMain.Current?.ToolsSubViewModel?.TriggerCacheOutdated();
+                InvokeLayerBitmapChange();
             }
         }
 
@@ -121,6 +122,7 @@ namespace PixiEditor.Models.Layers
                             new object[] { LayerGuid },
                             "Change layer visibility"));
                     IsVisible = value;
+                    InvokeLayerBitmapChange();
                 }
             }
         }
@@ -135,13 +137,18 @@ namespace PixiEditor.Models.Layers
             }
         }
 
-        public WriteableBitmap LayerBitmap
+        public Surface LayerBitmap
         {
             get => layerBitmap;
-            set
+            private set
             {
+                Int32Rect prevRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
                 layerBitmap = value;
+                Width = layerBitmap.Width;
+                Height = layerBitmap.Height;
+                Int32Rect curRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
                 RaisePropertyChanged(nameof(LayerBitmap));
+                InvokeLayerBitmapChange(prevRect.Expand(curRect));
             }
         }
 
@@ -150,11 +157,10 @@ namespace PixiEditor.Models.Layers
             get => opacity;
             set
             {
-                if (SetProperty(ref opacity, value))
-                {
-                    RaisePropertyChanged(nameof(OpacityUndoTriggerable));
-                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
-                }
+                opacity = value;
+                RaisePropertyChanged(nameof(OpacityUndoTriggerable));
+                ViewModelMain.Current?.ToolsSubViewModel?.TriggerCacheOutdated();
+                InvokeLayerBitmapChange();
             }
         }
 
@@ -188,8 +194,11 @@ namespace PixiEditor.Models.Layers
             get => offset;
             set
             {
+                Int32Rect prevRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
                 offset = value;
-                RaisePropertyChanged("Offset");
+                Int32Rect curRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
+                RaisePropertyChanged(nameof(Offset));
+                InvokeLayerBitmapChange(prevRect.Expand(curRect));
             }
         }
 
@@ -197,6 +206,23 @@ namespace PixiEditor.Models.Layers
 
         public int MaxHeight { get; set; } = int.MaxValue;
 
+        public bool IsReset { get; private set; }
+
+        public event EventHandler<Int32Rect> LayerBitmapChanged;
+
+        public void InvokeLayerBitmapChange()
+        {
+            IsReset = false;
+            LayerBitmapChanged?.Invoke(this, new Int32Rect(OffsetX, OffsetY, Width, Height));
+        }
+
+        public void InvokeLayerBitmapChange(Int32Rect dirtyArea)
+        {
+            IsReset = false;
+            LayerBitmapChanged?.Invoke(this, dirtyArea);
+        }
+
+
         /// <summary>
         /// Changes Guid of layer.
         /// </summary>
@@ -217,7 +243,7 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         public Layer Clone(bool generateNewGuid = false)
         {
-            return new Layer(Name, LayerBitmap.Clone())
+            return new Layer(Name, new Surface(LayerBitmap))
             {
                 IsVisible = IsVisible,
                 Offset = Offset,
@@ -244,7 +270,7 @@ namespace PixiEditor.Models.Layers
         /// <param name="newMaxHeight">New layer maximum height, this should be document height.</param>
         public void Resize(int width, int height, int newMaxWidth, int newMaxHeight)
         {
-            LayerBitmap = LayerBitmap.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            LayerBitmap = LayerBitmap.ResizeNearestNeighbor(width, height);
             Width = width;
             Height = height;
             MaxWidth = newMaxWidth;
@@ -265,10 +291,10 @@ namespace PixiEditor.Models.Layers
         /// <param name="x">Viewport relative X.</param>
         /// <param name="y">Viewport relative Y.</param>
         /// <returns>Color of a pixel.</returns>
-        public Color GetPixelWithOffset(int x, int y)
+        public SKColor GetPixelWithOffset(int x, int y)
         {
-            Coordinates cords = GetRelativePosition(new Coordinates(x, y));
-            return GetPixel(cords.X, cords.Y);
+            //This does not use GetRelativePosition for better performance
+            return GetPixel(x - OffsetX, y - OffsetY);
         }
 
         /// <summary>
@@ -277,26 +303,24 @@ namespace PixiEditor.Models.Layers
         /// <param name="x">X coordinate.</param>
         /// <param name="y">Y Coordinate.</param>
         /// <returns>Color of pixel, if out of bounds, returns transparent pixel.</returns>
-        public Color GetPixel(int x, int y)
+        public SKColor GetPixel(int x, int y)
         {
             if (x > Width - 1 || x < 0 || y > Height - 1 || y < 0)
             {
-                return Color.FromArgb(0, 0, 0, 0);
+                return SKColors.Empty;
             }
 
-            return LayerBitmap.GetPixel(x, y);
+            return LayerBitmap.GetSRGBPixel(x, y);
         }
 
-        /// <summary>
-        ///     Applies pixel to layer.
-        /// </summary>
-        /// <param name="coordinates">Position of pixel.</param>
-        /// <param name="color">Color of pixel.</param>
-        /// <param name="dynamicResize">Resizes bitmap to fit content.</param>
-        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap.</param>
-        public void SetPixel(Coordinates coordinates, Color color, bool dynamicResize = true, bool applyOffset = true)
+        public void SetPixelWithOffset(Coordinates coordinates, SKColor color)
         {
-            SetPixels(BitmapPixelChanges.FromSingleColoredArray(new[] { coordinates }, color), dynamicResize, applyOffset);
+            LayerBitmap.SetSRGBPixel(coordinates.X - OffsetX, coordinates.Y - OffsetY, color);
+        }
+
+        public void SetPixelWithOffset(int x, int y, SKColor color)
+        {
+            LayerBitmap.SetSRGBPixel(x - OffsetX, y - OffsetY, color);
         }
 
         /// <summary>
@@ -312,32 +336,40 @@ namespace PixiEditor.Models.Layers
                 return;
             }
 
-            if (dynamicResize)
+            if (applyOffset)
             {
-                DynamicResize(pixels);
+                pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
             }
 
-            if (applyOffset)
+            if (dynamicResize)
             {
-                pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
+                DynamicResize(pixels);
             }
 
             LastRelativeCoordinates = pixels.ChangedPixels;
 
-            using (BitmapContext ctx = LayerBitmap.GetBitmapContext())
+            int minX = int.MaxValue;
+            int maxX = int.MinValue;
+            int minY = int.MaxValue;
+            int maxY = int.MinValue;
+
+            foreach (KeyValuePair<Coordinates, SKColor> coords in pixels.ChangedPixels)
             {
-                foreach (KeyValuePair<Coordinates, Color> coords in pixels.ChangedPixels)
+                if (OutOfBounds(coords.Key))
                 {
-                    if (OutOfBounds(coords.Key))
-                    {
-                        continue;
-                    }
-
-                    ctx.WriteableBitmap.SetPixel(coords.Key.X, coords.Key.Y, coords.Value);
+                    continue;
                 }
+
+                LayerBitmap.SetSRGBPixel(coords.Key.X, coords.Key.Y, coords.Value);
+                minX = Math.Min(minX, coords.Key.X);
+                minY = Math.Min(minY, coords.Key.Y);
+                maxX = Math.Max(maxX, coords.Key.X);
+                maxY = Math.Max(maxY, coords.Key.Y);
             }
 
             ClipIfNecessary();
+            if (minX != int.MaxValue)
+                InvokeLayerBitmapChange(new Int32Rect(minX + OffsetX, minY + OffsetY, maxX - minX + 1, maxY - minY + 1));
         }
 
         /// <summary>
@@ -355,6 +387,15 @@ namespace PixiEditor.Models.Layers
             return result;
         }
 
+        public void CreateNewBitmap(int width, int height)
+        {
+            LayerBitmap = new Surface(width, height);
+
+            Width = width;
+            Height = height;
+        }
+
+
         /// <summary>
         ///     Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth.
         /// </summary>
@@ -366,24 +407,16 @@ namespace PixiEditor.Models.Layers
             }
 
             ResetOffset(pixels);
-            Tuple<DoubleCords, bool> borderData = ExtractBorderData(pixels);
-            DoubleCords minMaxCords = borderData.Item1;
-            int newMaxX = minMaxCords.Coords2.X - OffsetX;
-            int newMaxY = minMaxCords.Coords2.Y - OffsetY;
-            int newMinX = minMaxCords.Coords1.X - OffsetX;
-            int newMinY = minMaxCords.Coords1.Y - OffsetY;
-
-            if (!(pixels.WasBuiltAsSingleColored && pixels.ChangedPixels.First().Value.A == 0))
+            Tuple<DoubleCoords, bool> borderData = ExtractBorderData(pixels);
+            DoubleCoords minMaxCords = borderData.Item1;
+            int newMaxX = minMaxCords.Coords2.X;
+            int newMaxY = minMaxCords.Coords2.Y;
+            int newMinX = minMaxCords.Coords1.X;
+            int newMinY = minMaxCords.Coords1.Y;
+
+            if (!(pixels.WasBuiltAsSingleColored && pixels.ChangedPixels.First().Value.Alpha == 0))
             {
-                if ((newMaxX + 1 > Width && Width < MaxWidth) || (newMaxY + 1 > Height && Height < MaxHeight))
-                {
-                    IncreaseSizeToBottomAndRight(newMaxX, newMaxY);
-                }
-
-                if ((newMinX < 0 && Width < MaxWidth) || (newMinY < 0 && Height < MaxHeight))
-                {
-                    IncreaseSizeToTopAndLeft(newMinX, newMinY);
-                }
+                DynamicResizeRelative(newMaxX, newMaxY, newMinX, newMinY);
             }
 
             // if clip is requested
@@ -393,12 +426,47 @@ namespace PixiEditor.Models.Layers
             }
         }
 
+        public void DynamicResizeAbsolute(Int32Rect newSize)
+        {
+            newSize = newSize.Intersect(new Int32Rect(0, 0, MaxWidth, MaxHeight));
+            if (newSize.IsEmpty)
+                return;
+            if (IsReset)
+            {
+                Offset = new Thickness(newSize.X, newSize.Y, 0, 0);
+            }
+
+            int relX = newSize.X - OffsetX;
+            int relY = newSize.Y - OffsetY;
+            int maxX = relX + newSize.Width - 1;
+            int maxY = relY + newSize.Height - 1;
+
+            DynamicResizeRelative(maxX, maxY, relX, relY);
+        }
+
         /// <summary>
-        ///     Changes size of bitmap to fit content.
+        ///     Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth.
         /// </summary>
-        public void ClipCanvas()
+        public void DynamicResizeRelative(int newMaxX, int newMaxY, int newMinX, int newMinY)
+        {
+            if ((newMaxX + 1 > Width && Width < MaxWidth) || (newMaxY + 1 > Height && Height < MaxHeight))
+            {
+                newMaxX = Math.Max(newMaxX, (int)(Width * 1.5f));
+                newMaxY = Math.Max(newMaxY, (int)(Height * 1.5f));
+                IncreaseSizeToBottomAndRight(newMaxX, newMaxY);
+            }
+
+            if ((newMinX < 0 && Width < MaxWidth) || (newMinY < 0 && Height < MaxHeight))
+            {
+                newMinX = Math.Min(newMinX, Width - (int)(Width * 1.5f));
+                newMinY = Math.Min(newMinY, Height - (int)(Height * 1.5f));
+                IncreaseSizeToTopAndLeft(newMinX, newMinY);
+            }
+        }
+
+        public Int32Rect GetContentDimensions()
         {
-            DoubleCords points = GetEdgePoints();
+            DoubleCoords points = GetEdgePoints();
             int smallestX = points.Coords1.X;
             int smallestY = points.Coords1.Y;
             int biggestX = points.Coords2.X;
@@ -406,22 +474,46 @@ namespace PixiEditor.Models.Layers
 
             if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
             {
-                return;
+                return Int32Rect.Empty;
             }
 
             int width = biggestX - smallestX + 1;
             int height = biggestY - smallestY + 1;
-            ResizeCanvas(0, 0, smallestX, smallestY, width, height);
-            Offset = new Thickness(OffsetX + smallestX, OffsetY + smallestY, 0, 0);
+            return new Int32Rect(smallestX, smallestY, width, height);
         }
 
         /// <summary>
-        ///     Clears bitmap.
+        ///     Changes size of bitmap to fit content.
         /// </summary>
-        public void Clear()
+        public void ClipCanvas()
         {
-            LayerBitmap.Clear();
-            ClipCanvas();
+            var dimensions = GetContentDimensions();
+            if (dimensions == Int32Rect.Empty) return;
+
+            ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
+            Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);
+        }
+
+        public void Reset()
+        {
+            if (IsReset)
+                return;
+            var dirtyRect = new Int32Rect(OffsetX, OffsetY, Width, Height);
+            LayerBitmap?.Dispose();
+            LayerBitmap = new Surface(1, 1);
+            Width = 1;
+            Height = 1;
+            Offset = new Thickness(0, 0, 0, 0);
+            IsReset = true;
+            LayerBitmapChanged?.Invoke(this, dirtyRect);
+        }
+
+        public void ClearCanvas()
+        {
+            if (IsReset)
+                return;
+            LayerBitmap.SkiaSurface.Canvas.Clear();
+            InvokeLayerBitmapChange();
         }
 
         /// <summary>
@@ -429,20 +521,46 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         public byte[] ConvertBitmapToBytes()
         {
-            LayerBitmap.Lock();
-            byte[] byteArray = LayerBitmap.ToByteArray();
-            LayerBitmap.Unlock();
-            return byteArray;
+            return LayerBitmap.ToByteArray();
+        }
+
+        public SKRectI GetRect() => SKRectI.Create(OffsetX, OffsetY, Width, Height);
+
+        public void CropIntersect(SKRectI rect)
+        {
+            SKRectI layerRect = GetRect();
+            SKRectI intersect = SKRectI.Intersect(layerRect, rect);
+
+            Crop(intersect);
         }
 
-        private Dictionary<Coordinates, Color> GetRelativePosition(Dictionary<Coordinates, Color> changedPixels)
+        public void Crop(SKRectI intersect)
+        {
+            if (intersect == SKRectI.Empty)
+            {
+                return;
+            }
+
+            using var oldSurface = LayerBitmap;
+
+            int offsetX = (int)(Offset.Left - intersect.Left);
+            int offsetY = (int)(Offset.Top - intersect.Top);
+
+            Width = intersect.Width;
+            Height = intersect.Height;
+            LayerBitmap = LayerBitmap.Crop(offsetX, offsetY, Width, Height);
+
+            Offset = new(intersect.Left, intersect.Top, 0, 0);
+        }
+
+        private Dictionary<Coordinates, SKColor> GetRelativePosition(Dictionary<Coordinates, SKColor> changedPixels)
         {
             return changedPixels.ToDictionary(
                 d => new Coordinates(d.Key.X - OffsetX, d.Key.Y - OffsetY),
                 d => d.Value);
         }
 
-        private Tuple<DoubleCords, bool> ExtractBorderData(BitmapPixelChanges pixels)
+        private Tuple<DoubleCoords, bool> ExtractBorderData(BitmapPixelChanges pixels)
         {
             Coordinates firstCords = pixels.ChangedPixels.First().Key;
             int minX = firstCords.X;
@@ -451,7 +569,7 @@ namespace PixiEditor.Models.Layers
             int maxY = minY;
             bool clipRequested = false;
 
-            foreach (KeyValuePair<Coordinates, Color> pixel in pixels.ChangedPixels)
+            foreach (KeyValuePair<Coordinates, SKColor> pixel in pixels.ChangedPixels)
             {
                 if (pixel.Key.X < minX)
                 {
@@ -471,14 +589,14 @@ namespace PixiEditor.Models.Layers
                     maxY = pixel.Key.Y;
                 }
 
-                if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.A == 0)
+                if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.Alpha == 0)
                 {
                     clipRequested = true;
                 }
             }
 
-            return new Tuple<DoubleCords, bool>(
-                new DoubleCords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
+            return new Tuple<DoubleCoords, bool>(
+                new DoubleCoords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
         }
 
         private bool IsBorderPixel(Coordinates cords)
@@ -508,8 +626,8 @@ namespace PixiEditor.Models.Layers
                 return;
             }
 
-            newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 0, MaxWidth - OffsetX);
-            newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 0, MaxHeight - OffsetY);
+            newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 1, MaxWidth - OffsetX);
+            newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 1, MaxHeight - OffsetY);
 
             ResizeCanvas(0, 0, 0, 0, newMaxX, newMaxY);
         }
@@ -534,12 +652,12 @@ namespace PixiEditor.Models.Layers
             ResizeCanvas(offsetX, offsetY, 0, 0, newWidth, newHeight);
         }
 
-        private DoubleCords GetEdgePoints()
+        private DoubleCoords GetEdgePoints()
         {
             Coordinates smallestPixel = CoordinatesCalculator.FindMinEdgeNonTransparentPixel(LayerBitmap);
             Coordinates biggestPixel = CoordinatesCalculator.FindMostEdgeNonTransparentPixel(LayerBitmap);
 
-            return new DoubleCords(smallestPixel, biggestPixel);
+            return new DoubleCoords(smallestPixel, biggestPixel);
         }
 
         private void ResetOffset(BitmapPixelChanges pixels)
@@ -557,26 +675,12 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         private void ResizeCanvas(int offsetX, int offsetY, int offsetXSrc, int offsetYSrc, int newWidth, int newHeight)
         {
-            int iteratorHeight = Height > newHeight ? newHeight : Height;
-            int count = Width > newWidth ? newWidth : Width;
-
-            using (BitmapContext srcContext = LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
-            {
-                WriteableBitmap result = BitmapFactory.New(newWidth, newHeight);
-                using (BitmapContext destContext = result.GetBitmapContext())
-                {
-                    for (int line = 0; line < iteratorHeight; line++)
-                    {
-                        int srcOff = (((offsetYSrc + line) * Width) + offsetXSrc) * SizeOfArgb;
-                        int dstOff = (((offsetY + line) * newWidth) + offsetX) * SizeOfArgb;
-                        BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, count * SizeOfArgb);
-                    }
-
-                    LayerBitmap = result;
-                    Width = newWidth;
-                    Height = newHeight;
-                }
-            }
+            Surface result = new Surface(newWidth, newHeight);
+            LayerBitmap.SkiaSurface.Draw(result.SkiaSurface.Canvas, offsetX - offsetXSrc, offsetY - offsetYSrc, Surface.ReplacingPaint);
+            LayerBitmap?.Dispose();
+            LayerBitmap = result;
+            Width = newWidth;
+            Height = newHeight;
         }
     }
 }

+ 6 - 7
PixiEditor/Models/Layers/LayerHelper.cs

@@ -1,10 +1,9 @@
-using System;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.ViewModels;
+using System;
+using System.Linq;
+using System.Windows;
 
 namespace PixiEditor.Models.Layers
 {
@@ -70,7 +69,7 @@ namespace PixiEditor.Models.Layers
             int height = yCloser.Height + offsetY + yOther.Height;
 
             // Merge both layers into a bitmap
-            WriteableBitmap mergedBitmap = BitmapUtils.CombineLayers((int)documentsSize.X, (int)documentsSize.Y, new Layer[] { thisLayer, otherLayer });
+            Surface mergedBitmap = BitmapUtils.CombineLayers(new Int32Rect(0, 0, (int)documentsSize.X, (int)documentsSize.Y), new Layer[] { thisLayer, otherLayer });
             mergedBitmap = mergedBitmap.Crop(xCloser.OffsetX, yCloser.OffsetY, width, height);
 
             // Create the new layer with the merged bitmap
@@ -87,4 +86,4 @@ namespace PixiEditor.Models.Layers
             return MergeWith(thisLayer, otherLayer, newName, new Vector(documentWidth, documentHeight));
         }
     }
-}
+}

+ 30 - 5
PixiEditor/Models/Layers/Utils/LayerStructureUtils.cs

@@ -1,8 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace PixiEditor.Models.Layers.Utils
 {
@@ -32,5 +28,34 @@ namespace PixiEditor.Models.Layers.Utils
 
             return Math.Clamp(finalOpacity, 0f, 1f);
         }
+
+        /// <summary>
+        /// Gets final layer IsVisible taking into consideration group visibility.
+        /// </summary>
+        /// <param name="layer">Layer to check.</param>
+        /// <returns>True if is visible, false if at least parent is not visible or layer itself is invisible.</returns>
+        public static bool GetFinalLayerIsVisible(Layer layer, LayerStructure structure)
+        {
+            if (!layer.IsVisible)
+            {
+                return false;
+            }
+
+            var group = structure.GetGroupByLayer(layer.LayerGuid);
+            bool atLeastOneParentIsInvisible = false;
+            GuidStructureItem groupToCheck = group;
+            while (groupToCheck != null)
+            {
+                if (!groupToCheck.IsVisible)
+                {
+                    atLeastOneParentIsInvisible = true;
+                    break;
+                }
+
+                groupToCheck = groupToCheck.Parent;
+            }
+
+            return !atLeastOneParentIsInvisible;
+        }
     }
-}
+}

+ 55 - 7
PixiEditor/Models/Position/Coordinates.cs

@@ -1,9 +1,17 @@
-using System;
+using SkiaSharp;
+using System;
+using System.Diagnostics;
+using System.Windows;
 
 namespace PixiEditor.Models.Position
 {
+    [DebuggerDisplay("({DebuggerDisplay,nq})")]
     public struct Coordinates
     {
+        public static Coordinates Zero => new Coordinates(0, 0);
+
+        private string DebuggerDisplay => ToString();
+
         public Coordinates(int x, int y)
         {
             X = x;
@@ -14,6 +22,31 @@ namespace PixiEditor.Models.Position
 
         public int Y { get; set; }
 
+        public static implicit operator Coordinates((int width, int height) tuple)
+        {
+            return new Coordinates(tuple.width, tuple.height);
+        }
+
+        public static Coordinates operator -(Coordinates coordiantes, Thickness thickness)
+        {
+            return new Coordinates(coordiantes.X - (int)thickness.Left, coordiantes.Y - (int)thickness.Top);
+        }
+
+        public static Coordinates operator -(Coordinates coordiantes, int size)
+        {
+            return new Coordinates(coordiantes.X - size, coordiantes.Y - size);
+        }
+
+        public static Coordinates operator +(Coordinates coordiantes, int size)
+        {
+            return new Coordinates(coordiantes.X + size, coordiantes.Y + size);
+        }
+
+        public static Coordinates operator -(Coordinates coordiantes1, Coordinates coordinates2)
+        {
+            return new Coordinates(coordiantes1.X - coordinates2.X, coordiantes1.Y - coordinates2.Y);
+        }
+
         public static bool operator ==(Coordinates c1, Coordinates c2)
         {
             return c2.X == c1.X && c2.Y == c1.Y;
@@ -29,19 +62,34 @@ namespace PixiEditor.Models.Position
             return $"{X}, {Y}";
         }
 
-        public override bool Equals(object obj)
+        public string ToString(IFormatProvider provider)
+        {
+            return $"{X.ToString(provider)}, {Y.ToString(provider)}";
+        }
+
+        public static explicit operator SKPoint(Coordinates coordinates)
         {
-            if (obj is Coordinates coords)
-            {
-                return this == coords;
-            }
+            return new SKPoint(coordinates.X, coordinates.Y);
+        }
 
-            return false;
+        public static explicit operator SKPointI(Coordinates coordinates)
+        {
+            return new SKPointI(coordinates.X, coordinates.Y);
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is Coordinates coords && this == coords;
         }
 
         public override int GetHashCode()
         {
             return HashCode.Combine(X, Y);
         }
+
+        public SKPoint ToSKPoint()
+        {
+            return new SKPoint(X, Y);
+        }
     }
 }

+ 40 - 34
PixiEditor/Models/Position/CoordinatesCalculator.cs

@@ -1,7 +1,8 @@
-using System;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using SkiaSharp;
+using System;
 using System.Collections.Generic;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.Position
 {
@@ -12,7 +13,7 @@ namespace PixiEditor.Models.Position
         /// </summary>
         /// <param name="startPosition">Top left position of rectangle.</param>
         /// <param name="thickness">Thickness of rectangle.</param>
-        public static DoubleCords CalculateThicknessCenter(Coordinates startPosition, int thickness)
+        public static DoubleCoords CalculateThicknessCenter(Coordinates startPosition, int thickness)
         {
             int x1, x2, y1, y2;
             if (thickness % 2 == 0)
@@ -30,7 +31,7 @@ namespace PixiEditor.Models.Position
                 y1 = y2 - thickness;
             }
 
-            return new DoubleCords(new Coordinates(x1, y1), new Coordinates(x2 - 1, y2 - 1));
+            return new DoubleCoords(new Coordinates(x1, y1), new Coordinates(x2 - 1, y2 - 1));
         }
 
         public static Coordinates GetCenterPoint(Coordinates startingPoint, Coordinates endPoint)
@@ -63,7 +64,21 @@ namespace PixiEditor.Models.Position
             return coordinates;
         }
 
-        public static IEnumerable<Coordinates> RectangleToCoordinates(DoubleCords coordinates)
+        public static void DrawRectangle(Layer layer, SKColor color, int x1, int y1, int x2, int y2)
+        {
+            //TODO: use some kind of context
+            x2++;
+            y2++;
+            for (int y = y1; y < y1 + (y2 - y1); y++)
+            {
+                for (int x = x1; x < x1 + (x2 - x1); x++)
+                {
+                    layer.SetPixelWithOffset(x, y, color);
+                }
+            }
+        }
+
+        public static IEnumerable<Coordinates> RectangleToCoordinates(DoubleCoords coordinates)
         {
             return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X, coordinates.Coords2.Y);
         }
@@ -71,7 +86,7 @@ namespace PixiEditor.Models.Position
         /// <summary>
         ///     Returns first pixel coordinates in bitmap that is most top left on canvas.
         /// </summary>
-        public static Coordinates FindMinEdgeNonTransparentPixel(WriteableBitmap bitmap)
+        public static Coordinates FindMinEdgeNonTransparentPixel(Surface bitmap)
         {
             return new Coordinates(FindMinXNonTransparent(bitmap), FindMinYNonTransparent(bitmap));
         }
@@ -79,20 +94,18 @@ namespace PixiEditor.Models.Position
         /// <summary>
         ///     Returns last pixel coordinates that is most bottom right.
         /// </summary>
-        public static Coordinates FindMostEdgeNonTransparentPixel(WriteableBitmap bitmap)
+        public static Coordinates FindMostEdgeNonTransparentPixel(Surface bitmap)
         {
             return new Coordinates(FindMaxXNonTransparent(bitmap), FindMaxYNonTransparent(bitmap));
         }
 
-        public static int FindMinYNonTransparent(WriteableBitmap bitmap)
+        public static int FindMinYNonTransparent(Surface bitmap)
         {
-            Color transparent = Color.FromArgb(0, 0, 0, 0);
-            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
-            for (int y = 0; y < ctx.Height; y++)
+            for (int y = 0; y < bitmap.Height; y++)
             {
-                for (int x = 0; x < ctx.Width; x++)
+                for (int x = 0; x < bitmap.Width; x++)
                 {
-                    if (ctx.WriteableBitmap.GetPixel(x, y) != transparent)
+                    if (bitmap.GetSRGBPixel(x, y).Alpha != 0)
                     {
                         return y;
                     }
@@ -102,15 +115,13 @@ namespace PixiEditor.Models.Position
             return -1;
         }
 
-        public static int FindMinXNonTransparent(WriteableBitmap bitmap)
+        public static int FindMinXNonTransparent(Surface bitmap)
         {
-            Color transparent = Color.FromArgb(0, 0, 0, 0);
-            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
-            for (int x = 0; x < ctx.Width; x++)
+            for (int x = 0; x < bitmap.Width; x++)
             {
-                for (int y = 0; y < ctx.Height; y++)
+                for (int y = 0; y < bitmap.Height; y++)
                 {
-                    if (bitmap.GetPixel(x, y) != transparent)
+                    if (bitmap.GetSRGBPixel(x, y).Alpha != 0)
                     {
                         return x;
                     }
@@ -120,15 +131,13 @@ namespace PixiEditor.Models.Position
             return -1;
         }
 
-        public static int FindMaxYNonTransparent(WriteableBitmap bitmap)
+        public static int FindMaxYNonTransparent(Surface bitmap)
         {
-            Color transparent = Color.FromArgb(0, 0, 0, 0);
-            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
-            for (int y = ctx.Height - 1; y >= 0; y--)
+            for (int y = bitmap.Height - 1; y >= 0; y--)
             {
-                for (int x = ctx.Width - 1; x >= 0; x--)
+                for (int x = bitmap.Width - 1; x >= 0; x--)
                 {
-                    if (bitmap.GetPixel(x, y) != transparent)
+                    if (bitmap.GetSRGBPixel(x, y).Alpha != 0)
                     {
                         return y;
                     }
@@ -138,16 +147,13 @@ namespace PixiEditor.Models.Position
             return -1;
         }
 
-        public static int FindMaxXNonTransparent(WriteableBitmap bitmap)
+        public static int FindMaxXNonTransparent(Surface bitmap)
         {
-            Color transparent = Color.FromArgb(0, 0, 0, 0);
-
-            using BitmapContext ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
-            for (int x = ctx.Width - 1; x >= 0; x--)
+            for (int x = bitmap.Width - 1; x >= 0; x--)
             {
-                for (int y = ctx.Height - 1; y >= 0; y--)
+                for (int y = bitmap.Height - 1; y >= 0; y--)
                 {
-                    if (bitmap.GetPixel(x, y) != transparent)
+                    if (bitmap.GetSRGBPixel(x, y).Alpha != 0)
                     {
                         return x;
                     }
@@ -157,4 +163,4 @@ namespace PixiEditor.Models.Position
             return -1;
         }
     }
-}
+}

+ 82 - 0
PixiEditor/Models/Position/CropData.cs

@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace PixiEditor.Models.Position
+{
+    [StructLayout(LayoutKind.Explicit)]
+    public unsafe struct CropData
+    {
+        [FieldOffset(0)]
+        private readonly int _width;
+        [FieldOffset(4)]
+        private readonly int _height;
+        [FieldOffset(8)]
+        private readonly int _offsetX;
+        [FieldOffset(12)]
+        private readonly int _offsetY;
+
+        public int Width => _width;
+
+        public int Height => _height;
+
+        public int OffsetX => _offsetX;
+
+        public int OffsetY => _offsetY;
+
+        public CropData(int width, int height, int offsetX, int offsetY)
+        {
+            _width = width;
+            _height = height;
+            _offsetX = offsetX;
+            _offsetY = offsetY;
+        }
+
+        public static CropData FromByteArray(byte[] data)
+        {
+            if (data.Length != sizeof(CropData))
+            {
+                throw new ArgumentOutOfRangeException(nameof(data), $"data must be {sizeof(CropData)} long");
+            }
+
+            fixed (void* ptr = data)
+            {
+                return Marshal.PtrToStructure<CropData>(new IntPtr(ptr));
+            }
+        }
+
+        public static CropData FromStream(Stream stream)
+        {
+            if (stream.Length < sizeof(CropData))
+            {
+                throw new ArgumentOutOfRangeException(nameof(stream), $"The specified stream must be at least {sizeof(CropData)} bytes long");
+            }
+
+            byte[] buffer = new byte[sizeof(CropData)];
+            stream.Read(buffer);
+
+            return FromByteArray(buffer);
+        }
+
+        public byte[] ToByteArray()
+        {
+            IntPtr ptr = Marshal.AllocHGlobal(sizeof(CropData));
+            Marshal.StructureToPtr(this, ptr, true);
+
+            Span<byte> bytes = new Span<byte>(ptr.ToPointer(), sizeof(CropData));
+            byte[] array = bytes.ToArray();
+
+            Marshal.FreeHGlobal(ptr);
+
+            return array;
+        }
+
+        public MemoryStream ToStream()
+        {
+            MemoryStream stream = new();
+            stream.Write(ToByteArray());
+            return stream;
+        }
+
+    }
+}

+ 14 - 14
PixiEditor/Models/Position/DoubleCords.cs → PixiEditor/Models/Position/DoubleCoords.cs

@@ -1,15 +1,15 @@
-namespace PixiEditor.Models.Position
-{
-    public struct DoubleCords
-    {
-        public DoubleCords(Coordinates cords1, Coordinates cords2)
-        {
-            Coords1 = cords1;
-            Coords2 = cords2;
-        }
-
-        public Coordinates Coords1 { get; set; }
-
-        public Coordinates Coords2 { get; set; }
-    }
+namespace PixiEditor.Models.Position
+{
+    public struct DoubleCoords
+    {
+        public DoubleCoords(Coordinates cords1, Coordinates cords2)
+        {
+            Coords1 = cords1;
+            Coords2 = cords2;
+        }
+
+        public Coordinates Coords1 { get; set; }
+
+        public Coordinates Coords2 { get; set; }
+    }
 }

+ 62 - 0
PixiEditor/Models/Services/DocumentProvider.cs

@@ -0,0 +1,62 @@
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using System.Collections.Generic;
+
+namespace PixiEditor.Models.Services
+{
+    /// <summary>
+    /// Provides the active document and it's values like active layer and reference layer
+    /// </summary>
+    public class DocumentProvider
+    {
+        private readonly BitmapManager _bitmapManager;
+
+        public DocumentProvider(BitmapManager bitmapManager)
+        {
+            _bitmapManager = bitmapManager;
+        }
+
+        /// <summary>
+        /// Gets all opened documents
+        /// </summary>
+        public IEnumerable<Document> GetDocuments() => _bitmapManager.Documents;
+
+        /// <summary>
+        /// Gets the active document
+        /// </summary>
+        public Document GetDocument() => _bitmapManager.ActiveDocument;
+
+        /// <summary>
+        /// Get the layers of the opened document
+        /// </summary>
+        public IEnumerable<Layer> GetLayers() => _bitmapManager.ActiveDocument?.Layers;
+
+        /// <summary>
+        /// Gets the active layer
+        /// </summary>
+        public Layer GetLayer() => _bitmapManager.ActiveLayer;
+
+        /// <summary>
+        /// Gets the surface of the active layer
+        /// </summary>
+        public Surface GetSurface() => _bitmapManager.ActiveLayer?.LayerBitmap;
+
+        /// <summary>
+        /// Gets the reference layer of the active document
+        /// </summary>
+        public Layer GetReferenceLayer() => _bitmapManager.ActiveDocument?.ReferenceLayer;
+
+        public Surface GetReferenceSurface() => _bitmapManager.ActiveDocument?.ReferenceLayer?.LayerBitmap;
+
+        /// <summary>
+        /// Gets the renderer for the active document
+        /// </summary>
+        public LayerStackRenderer GetRenderer() => _bitmapManager.ActiveDocument?.Renderer;
+
+        /// <summary>
+        /// Gets the renderer for the reference layer of the active document
+        /// </summary>
+        public SingleLayerRenderer GetReferenceRenderer() => _bitmapManager.ActiveDocument?.ReferenceLayerRenderer;
+    }
+}

+ 22 - 8
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -1,10 +1,13 @@
 using System;
 using System.Collections.Generic;
 using System.Windows.Documents;
+using System.Windows.Input;
 using System.Windows.Media;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Undo;
+using SkiaSharp;
 
 namespace PixiEditor.Models.Tools
 {
@@ -17,20 +20,31 @@ namespace PixiEditor.Models.Tools
         public bool UseDefaultUndoMethod { get; set; } = true;
         public virtual bool UsesShift => true;
 
-        private readonly LayerChange[] onlyLayerArr = new LayerChange[] { new LayerChange(BitmapPixelChanges.Empty, Guid.Empty) };
+        private StorageBasedChange _change;
 
-        public abstract LayerChange[] Use(Layer layer, List<Coordinates> mouseMove, Color color);
+        public abstract void Use(Layer layer, List<Coordinates> mouseMove, SKColor color);
 
-        protected LayerChange[] Only(BitmapPixelChanges changes, Layer layer)
+        /// <summary>
+        /// Executes undo adding procedure.
+        /// </summary>
+        /// <param name="document">Active document</param>
+        /// <remarks>When overriding, set UseDefaultUndoMethod to false.</remarks>
+        public override void AddUndoProcess(Document document)
         {
-            onlyLayerArr[0] = new LayerChange(changes, layer);
-            return onlyLayerArr;
+            if (!UseDefaultUndoMethod) return;
+
+            var args = new object[] { _change.Document };
+            document.UndoManager.AddUndoChange(_change.ToChange(StorageBasedChange.BasicUndoProcess, args));
+            _change = null;
         }
 
-        protected LayerChange[] Only(BitmapPixelChanges changes, Guid layerGuid)
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
-            onlyLayerArr[0] = new LayerChange(changes, layerGuid);
-            return onlyLayerArr;
+            if (UseDefaultUndoMethod && e.LeftButton == MouseButtonState.Pressed)
+            {
+                Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
+                _change = new StorageBasedChange(doc, new[] { doc.ActiveLayer }, true);
+            }
         }
     }
 }

+ 13 - 0
PixiEditor/Models/Tools/Brushes/Brush.cs

@@ -0,0 +1,13 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public abstract class Brush
+    {
+        public abstract void Draw(Layer layer, int toolSize, Coordinates coordinates, SKPaint paint);
+    }
+}

+ 22 - 0
PixiEditor/Models/Tools/Brushes/CircleBrush.cs

@@ -0,0 +1,22 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using SkiaSharp;
+using System;
+
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public class CircleBrush : Brush
+    {
+        public override void Draw(Layer layer, int toolSize, Coordinates coordinates, SKPaint paint)
+        {
+            int halfSize = (int)Math.Ceiling(toolSize / 2f);
+            int modifier = toolSize % 2 != 0 ? 1 : 0;
+            Coordinates topLeft = new Coordinates(coordinates.X - halfSize + modifier, coordinates.Y - halfSize + modifier);
+            Coordinates bottomRight = new Coordinates(coordinates.X + halfSize - 1, coordinates.Y + halfSize - 1);
+
+            CircleTool.DrawEllipseFromCoordinates(layer, topLeft, bottomRight, paint.Color, paint.Color, 1, true);
+        }
+    }
+}

+ 14 - 0
PixiEditor/Models/Tools/Brushes/CircleBrushOverrides/InterestingShapeBrush.cs

@@ -0,0 +1,14 @@
+namespace PixiEditor.Models.Tools.Brushes
+{
+    public class InterestingShapeBrush : MatrixBrush
+    {
+        public static readonly int[,] InterestingShapeMatrix = new int[,]
+        {
+            { 1, 1, 1 },
+            { 0, 1, 0 },
+            { 0, 1, 0 }
+        };
+
+        public override int[,] BrushMatrix => InterestingShapeMatrix;
+    }
+}

+ 25 - 0
PixiEditor/Models/Tools/FloodFillRange.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Models.Tools
+{
+    /// <summary>
+    /// Represents a linear range to be filled and branched from.
+    /// </summary>
+    public struct FloodFillRange
+    {
+        public int StartX;
+        public int EndX;
+        public int Y;
+
+        public FloodFillRange(int startX, int endX, int y)
+        {
+            StartX = startX;
+            EndX = endX;
+            Y = y;
+        }
+    }
+}

+ 94 - 0
PixiEditor/Models/Tools/MatrixBrush.cs

@@ -0,0 +1,94 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Brushes;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+
+namespace PixiEditor.Models.Tools
+{
+    public abstract class MatrixBrush : Brush
+    {
+        public abstract int[,] BrushMatrix { get; }
+
+        public SKPoint[] BakedMatrix => _cachedBakedMatrix;
+
+
+        private SKPoint[] _cachedBakedMatrix;
+        private SKPoint[] _cachedGetAtPointBakedMatrix;
+
+        public MatrixBrush()
+        {
+            InitMatrix();
+        }
+
+        public override void Draw(Layer layer, int toolSize, Coordinates coordinates, SKPaint paint)
+        {
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoints(SKPointMode.Points, GetAtPoint(coordinates, layer.OffsetX, layer.OffsetY), paint);
+        }
+
+        //We can easily handle .pixi to brush parsing
+
+        /// <summary>
+        ///     Creates an SKPoint[] array from BrushMatrix. All values grater than 0 will be placed in final array.
+        /// </summary>
+        /// <returns>Points array, ready to be drawn on Skia canvas.</returns>
+        public virtual SKPoint[] BakeMatrix()
+        {
+            List<SKPoint> result = new List<SKPoint>();
+
+            int brushHeight = BrushMatrix.GetLength(0);
+            int brushWidth = BrushMatrix.GetLength(1);
+
+            int centerX = (int)Math.Floor(brushWidth / 2f);
+            int centerY = (int)Math.Floor(brushHeight / 2f);
+
+            for (int i = 0; i < brushHeight; i++)
+            {
+                for (int j = 0; j < brushWidth; j++)
+                {
+                    if (BrushMatrix[i, j] > 0)
+                    {
+                        result.Add(new SKPoint(centerX - j, centerY - i));
+                    }
+                }
+            }
+
+            return result.ToArray();
+        }
+
+        /// <summary>
+        ///     Calculates BrushMatrix for given point.
+        /// </summary>
+        /// <param name="point">Point to calculate BrushMatrix for.</param>
+        /// <returns>SKPoints for given coordinate.</returns>
+        public SKPoint[] GetAtPoint(Coordinates point, int offsetX, int offsetY)
+        {
+            if (_cachedGetAtPointBakedMatrix == null)
+            {
+                InitMatrix();
+            }
+
+            for (int i = 0; i < _cachedGetAtPointBakedMatrix.Length; i++)
+            {
+                _cachedGetAtPointBakedMatrix[i] = new SKPoint(
+                    _cachedBakedMatrix[i].X + point.X - offsetX,
+                    _cachedBakedMatrix[i].Y + point.Y - offsetY);
+
+            }
+
+            return _cachedGetAtPointBakedMatrix;
+        }
+
+        private void InitMatrix()
+        {
+            if (_cachedBakedMatrix == null)
+            {
+                _cachedBakedMatrix = BakeMatrix();
+                _cachedGetAtPointBakedMatrix = new SKPoint[_cachedBakedMatrix.Length];
+                Array.Copy(_cachedBakedMatrix, _cachedGetAtPointBakedMatrix, BakedMatrix.Length);
+            }
+        }
+
+    }
+}

+ 15 - 14
PixiEditor/Models/Tools/ShapeTool.cs

@@ -2,16 +2,16 @@ using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using SkiaSharp;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows.Input;
-using System.Windows.Media;
 
 namespace PixiEditor.Models.Tools
 {
     public abstract class ShapeTool : BitmapOperationTool
     {
-        public static DoubleCords CalculateCoordinatesForShapeRotation(
+        public static DoubleCoords CalculateCoordinatesForShapeRotation(
             Coordinates startingCords,
             Coordinates secondCoordinates)
         {
@@ -19,33 +19,33 @@ namespace PixiEditor.Models.Tools
 
             if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
             {
-                return new DoubleCords(
+                return new DoubleCoords(
                     new Coordinates(currentCoordinates.X, currentCoordinates.Y),
                     new Coordinates(startingCords.X, startingCords.Y));
             }
 
             if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
             {
-                return new DoubleCords(
+                return new DoubleCoords(
                     new Coordinates(startingCords.X, startingCords.Y),
                     new Coordinates(currentCoordinates.X, currentCoordinates.Y));
             }
 
             if (startingCords.Y > currentCoordinates.Y)
             {
-                return new DoubleCords(
+                return new DoubleCoords(
                     new Coordinates(startingCords.X, currentCoordinates.Y),
                     new Coordinates(currentCoordinates.X, startingCords.Y));
             }
 
             if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
             {
-                return new DoubleCords(
+                return new DoubleCoords(
                     new Coordinates(currentCoordinates.X, startingCords.Y),
                     new Coordinates(startingCords.X, currentCoordinates.Y));
             }
 
-            return new DoubleCords(startingCords, secondCoordinates);
+            return new DoubleCoords(startingCords, secondCoordinates);
         }
 
         public ShapeTool()
@@ -56,19 +56,20 @@ namespace PixiEditor.Models.Tools
         }
 
         // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
-        public abstract override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color);
+        public abstract override void Use(Layer layer, List<Coordinates> coordinates, SKColor color);
 
-        protected static IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
+        public static void ThickenShape(Layer layer, SKColor color, IEnumerable<Coordinates> shape, int thickness)
         {
-            List<Coordinates> output = new List<Coordinates>();
             foreach (Coordinates item in shape)
             {
-                output.AddRange(
-                    CoordinatesCalculator.RectangleToCoordinates(
-                        CoordinatesCalculator.CalculateThicknessCenter(item, thickness)));
+                ThickenShape(layer, color, item, thickness);
             }
+        }
 
-            return output.Distinct();
+        protected static void ThickenShape(Layer layer, SKColor color, Coordinates coords, int thickness)
+        {
+            var dcords = CoordinatesCalculator.CalculateThicknessCenter(coords, thickness);
+            CoordinatesCalculator.DrawRectangle(layer, color, dcords.Coords1.X, dcords.Coords1.Y, dcords.Coords2.X, dcords.Coords2.Y);
         }
     }
 }

+ 50 - 37
PixiEditor/Models/Tools/Tool.cs

@@ -1,27 +1,29 @@
 using System;
+using System.Collections.Generic;
 using System.Text;
+using System.Windows;
 using System.Windows.Input;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-
-namespace PixiEditor.Models.Tools
-{
-    public abstract class Tool : NotifyableObject
-    {
-        private bool isActive;
-        private string actionDisplay = string.Empty;
+using PixiEditor.Models.Undo;
 
+namespace PixiEditor.Models.Tools
+{
+    public abstract class Tool : NotifyableObject
+    {
         public virtual string ToolName => GetType().Name.Replace("Tool", string.Empty);
 
         public virtual string DisplayName => ToolName.AddSpacesBeforeUppercaseLetters();
 
-        public virtual string ImagePath => $"/Images/Tools/{ToolName}Image.png";
+        public virtual string ImagePath => $"/Images/Tools/{ToolName}Image.png";
 
-        public virtual bool HideHighlight { get; }
+        public virtual bool HideHighlight { get; }
 
         public abstract string Tooltip { get; }
 
@@ -34,56 +36,67 @@ namespace PixiEditor.Models.Tools
                 RaisePropertyChanged("ActionDisplay");
             }
         }
-
-        public bool IsActive
-        {
-            get => isActive;
-            set
-            {
-                isActive = value;
-                RaisePropertyChanged("IsActive");
-            }
-        }
-
-        public Cursor Cursor { get; set; } = Cursors.Arrow;
-
-        public Toolbar Toolbar { get; set; } = new EmptyToolbar();
-
-        public bool CanStartOutsideCanvas { get; set; } = false;
-
+
+        public bool IsActive
+        {
+            get => isActive;
+            set
+            {
+                isActive = value;
+                RaisePropertyChanged("IsActive");
+            }
+        }
+
+        public Cursor Cursor { get; set; } = Cursors.Arrow;
+
+        public Toolbar Toolbar { get; set; } = new EmptyToolbar();
+
+        public bool CanStartOutsideCanvas { get; set; } = false;
+
+        private bool isActive;
+        private string actionDisplay = string.Empty;
+
         public virtual void OnMouseDown(MouseEventArgs e)
         {
         }
 
+        public virtual void AddUndoProcess(Document document)
+        {
+        }
+
         public virtual void OnMouseUp(MouseEventArgs e)
         {
         }
-
+
         public virtual void OnKeyDown(KeyEventArgs e)
         {
-        }
-
+        }
+
         public virtual void OnKeyUp(KeyEventArgs e)
         {
-        }
-
+        }
+
         public virtual void OnStart(Coordinates clickPosition)
         {
-        }
-
+        }
+
         public virtual void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         }
-
+
         public virtual void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
         }
-
+
         public virtual void OnMouseMove(MouseEventArgs e)
         {
         }
-
-        public virtual void AfterAddedUndo(UndoManager undoManager)
+
+        public virtual void OnSelected()
+        {
+        }
+
+        public virtual void OnDeselected()
         {
         }
     }

+ 0 - 71
PixiEditor/Models/Tools/ToolBuilder.cs

@@ -1,71 +0,0 @@
-using PixiEditor.Helpers;
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-
-namespace PixiEditor.Models.Tools
-{
-    /// <summary>
-    /// Handles Depdency Injection of tools
-    /// </summary>
-    public class ToolBuilder
-    {
-        private readonly IServiceProvider services;
-
-        private readonly List<Type> toBuild = new List<Type>();
-
-        public ToolBuilder(IServiceProvider services)
-        {
-            this.services = services;
-        }
-
-        /// <summary>
-        /// Constructs a new tool of type <typeparamref name="T"/> and injects all services of <paramref name="services"/>
-        /// </summary>
-        public static T BuildTool<T>(IServiceProvider services)
-            where T : Tool
-            => (T)BuildTool(typeof(T), services);
-
-        /// <summary>
-        /// Constructs a new tool of type <paramref name="type"/> and injects all services of <paramref name="services"/>
-        /// </summary>
-        public static Tool BuildTool(Type type, IServiceProvider services)
-        {
-            Tool tool = (Tool)services.Inject(type);
-
-            return tool;
-        }
-
-        /// <summary>
-        /// Adds a new tool of type <typeparamref name="T"/> to the building chain.
-        /// </summary>
-        public ToolBuilder Add<T>()
-            where T : Tool
-            => Add(typeof(T));
-
-        /// <summary>
-        /// Adds a new tool of type <paramref name="type"/> to the building chain.
-        /// </summary>
-        public ToolBuilder Add(Type type)
-        {
-            toBuild.Add(type);
-
-            return this;
-        }
-
-        /// <summary>
-        /// Builds all added tools.
-        /// </summary>
-        public IEnumerable<Tool> Build()
-        {
-            List<Tool> tools = new List<Tool>();
-
-            foreach (Type type in toBuild)
-            {
-                tools.Add(BuildTool(type, services));
-            }
-
-            return tools;
-        }
-    }
-}

+ 55 - 40
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -1,16 +1,19 @@
 using System;
 using System.Collections.Generic;
-using System.Windows.Controls;
 using System.Windows.Input;
+using System.Linq;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Colors;
-using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using SkiaSharp;
+using System.Windows;
+using PixiEditor.Helpers;
 
 namespace PixiEditor.Models.Tools.Tools
 {
@@ -19,90 +22,102 @@ namespace PixiEditor.Models.Tools.Tools
         private const float CorrectionFactor = 5f; // Initial correction factor
 
         private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
+        private List<DoubleCoords> circleCache = new List<DoubleCoords>();
+        private int cachedCircleSize = -1;
 
+        private string defaultActionDisplay = "Draw on pixels to make them brighter. Hold Ctrl to darken.";
         public BrightnessTool()
         {
-            ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
+            ActionDisplay = defaultActionDisplay;
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
 
-		public override bool UsesShift => false;
-		public override string Tooltip => "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
+        public override bool UsesShift => false;
+
+        public override string Tooltip => "Makes pixels brighter or darker (U). Hold Ctrl to make pixels darker.";
 
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
+            base.OnRecordingLeftMouseDown(e);
             pixelsVisited.Clear();
         }
 
         public override void OnKeyDown(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftCtrl)
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
             {
-                ActionDisplay = "Draw on pixel to make it darker. Release Ctrl to brighten.";
+                ActionDisplay = "Draw on pixels to make them darker. Release Ctrl to brighten.";
             }
         }
 
         public override void OnKeyUp(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftCtrl)
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
             {
-                ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
+                ActionDisplay = defaultActionDisplay;
             }
         }
 
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
             int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
             Mode = Toolbar.GetEnumSetting<BrightnessMode>("BrightnessMode").Value;
 
-            LayerChange[] layersChanges = new LayerChange[1];
-            if (Keyboard.IsKeyDown(Key.LeftCtrl))
+            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
             {
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor), layer);
+                ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor);
             }
             else
             {
-                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor), layer);
+                ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor);
             }
-
-            return layersChanges;
         }
 
-        public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
+        public void ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
         {
-            DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            IEnumerable<Coordinates> rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
-                centeredCoords.Coords1.X,
-                centeredCoords.Coords1.Y,
-                centeredCoords.Coords2.X,
-                centeredCoords.Coords2.Y);
-            BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
-
-            foreach (Coordinates coordinate in rectangleCoordinates)
+            if (cachedCircleSize != toolSize)
+                UpdateCircleCache(toolSize);
+
+            int radius = (int)Math.Ceiling(toolSize / 2f);
+            Int32Rect dirtyRect = new(coordinates.X - radius, coordinates.Y - radius, radius * 2, radius * 2);
+            layer.DynamicResizeAbsolute(dirtyRect);
+
+            foreach (var pair in circleCache)
             {
-                if (Mode == BrightnessMode.Default)
+                Coordinates left = pair.Coords1;
+                Coordinates right = pair.Coords2;
+                int y = left.Y + coordinates.Y;
+
+                for (int x = left.X + coordinates.X; x <= right.X + coordinates.X; x++)
                 {
-                    if (pixelsVisited.Contains(coordinate))
+                    if (Mode == BrightnessMode.Default)
                     {
-                        continue;
+                        Coordinates here = new(x, y);
+                        if (pixelsVisited.Contains(here))
+                            continue;
+
+                        pixelsVisited.Add(here);
                     }
 
-                    pixelsVisited.Add(coordinate);
+                    SKColor pixel = layer.GetPixelWithOffset(x, y);
+                    SKColor newColor = ExColor.ChangeColorBrightness(
+                        pixel,
+                        correctionFactor);
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x - layer.OffsetX, y - layer.OffsetY, newColor);
                 }
-
-                Color pixel = layer.GetPixelWithOffset(coordinate.X, coordinate.Y);
-                Color newColor = ExColor.ChangeColorBrightness(
-                    Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
-                    correctionFactor);
-                changes.ChangedPixels.Add(
-                    new Coordinates(coordinate.X, coordinate.Y),
-                    newColor);
             }
+            layer.InvokeLayerBitmapChange(dirtyRect);
+        }
 
-            return changes;
+        public void UpdateCircleCache(int newCircleSize)
+        {
+            cachedCircleSize = newCircleSize;
+            DoubleCoords rect = CoordinatesCalculator.CalculateThicknessCenter(new Coordinates(0, 0), newCircleSize);
+            List<Coordinates> circle = EllipseGenerator.GenerateEllipseFromRect(rect);
+            circleCache = EllipseGenerator.SplitEllipseIntoLines(circle);
         }
     }
-}
+}

+ 77 - 161
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -1,215 +1,131 @@
-using System;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using SkiaSharp;
+using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
+using System.Windows;
 using System.Windows.Input;
 using System.Windows.Media;
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class CircleTool : ShapeTool
     {
-        public CircleTool()
-        {
-            ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
-        }
 
-        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
-
-        public override void OnKeyDown(KeyEventArgs e)
+        public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
+            SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
         {
-            if (e.Key == Key.LeftShift)
+            DoubleCoords corners = CalculateCoordinatesForShapeRotation(first, second);
+            corners.Coords2 = new(corners.Coords2.X, corners.Coords2.Y);
+
+            int halfThickness = (int)Math.Ceiling(thickness / 2.0);
+            Int32Rect dirtyRect = new Int32Rect(
+                corners.Coords1.X - halfThickness,
+                corners.Coords1.Y - halfThickness,
+                corners.Coords2.X + halfThickness * 2 - corners.Coords1.X,
+                corners.Coords2.Y + halfThickness * 2 - corners.Coords1.Y);
+            layer.DynamicResizeAbsolute(dirtyRect);
+
+            using (SKPaint paint = new SKPaint())
             {
-                ActionDisplay = "Click and move mouse to draw an even circle.";
-            }
-        }
+                List<Coordinates> outline = EllipseGenerator.GenerateEllipseFromRect(corners);
+                if (hasFillColor)
+                {
+                    DrawEllipseFill(layer, fillColor, outline);
+                }
+                DrawEllipseOutline(layer, color, outline, thickness);
 
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key == Key.LeftShift)
-            {
-                ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
+                // An Idea, use Skia DrawOval for bigger sizes.
             }
+
+            layer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
+        public static void DrawEllipseFill(Layer layer, SKColor color, List<Coordinates> outlineCoordinates)
         {
-            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
-            IEnumerable<Coordinates> outline = CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, thickness);
-            BitmapPixelChanges pixels = BitmapPixelChanges.FromSingleColoredArray(outline, color);
-            if (Toolbar.GetSetting<BoolSetting>("Fill").Value)
-            {
-                Color fillColor = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
-                pixels.ChangedPixels.AddRangeNewOnly(
-                    BitmapPixelChanges.FromSingleColoredArray(CalculateFillForEllipse(outline), fillColor)
-                        .ChangedPixels);
-            }
+            if (!outlineCoordinates.Any())
+                return;
 
-            return new[] { new LayerChange(pixels, layer) };
-        }
+            int bottom = outlineCoordinates.Max(x => x.Y);
+            int top = outlineCoordinates.Min(x => x.Y);
 
-        /// <summary>
-        ///     Calculates ellipse points for specified coordinates and thickness.
-        /// </summary>
-        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
-        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
-        /// <param name="thickness">Thickness of ellipse.</param>
-        /// <param name="filled">Should ellipse be filled.</param>
-        /// <returns>Coordinates for ellipse.</returns>
-        public IEnumerable<Coordinates> CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness, bool filled)
-        {
-            List<Coordinates> output = new List<Coordinates>();
-            IEnumerable<Coordinates> outline = CreateEllipse(startCoordinates, endCoordinates, thickness);
-            output.AddRange(outline);
-            if (filled)
+            using SKPaint fillPaint = new();
+            fillPaint.Color = color;
+            fillPaint.BlendMode = SKBlendMode.Src;
+
+            for (int i = top + 1; i < bottom; i++)
             {
-                output.AddRange(CalculateFillForEllipse(outline));
-                return output.Distinct();
+                IEnumerable<Coordinates> rowCords = outlineCoordinates.Where(x => x.Y == i);
+                int right = rowCords.Max(x => x.X);
+                int left = rowCords.Min(x => x.X);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawLine(left - layer.OffsetX, i - layer.OffsetY, right - layer.OffsetX, i - layer.OffsetY, fillPaint);
             }
-
-            return output;
         }
 
         /// <summary>
         ///     Calculates ellipse points for specified coordinates and thickness.
         /// </summary>
-        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
-        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
         /// <param name="thickness">Thickness of ellipse.</param>
-        /// <returns>Coordinates for ellipse.</returns>
-        public IEnumerable<Coordinates> CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness)
+        public static void DrawEllipseOutline(Layer layer, SKColor color, List<Coordinates> ellipse, int thickness)
         {
-            double radiusX = (endCoordinates.X - startCoordinates.X) / 2.0;
-            double radiusY = (endCoordinates.Y - startCoordinates.Y) / 2.0;
-            double centerX = (startCoordinates.X + endCoordinates.X + 1) / 2.0;
-            double centerY = (startCoordinates.Y + endCoordinates.Y + 1) / 2.0;
-
-            List<Coordinates> output = new List<Coordinates>();
-            IEnumerable<Coordinates> ellipse = MidpointEllipse(radiusX, radiusY, centerX, centerY);
             if (thickness == 1)
             {
-                output.AddRange(ellipse);
-            }
-            else
-            {
-                output.AddRange(GetThickShape(ellipse, thickness));
-            }
-
-            return output.Distinct();
-        }
-
-        public IEnumerable<Coordinates> MidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY)
-        {
-            if (halfWidth < 1 || halfHeight < 1)
-            {
-                return FallbackRectangle(halfWidth, halfHeight, centerX, centerY);
-            }
-
-            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
-
-            // Make sure we are always at the center of a pixel
-            double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
-            double currentY = centerY + halfHeight;
-
-            List<Coordinates> outputCoordinates = new List<Coordinates>();
-
-            double currentSlope;
-
-            // from PI/2 to middle
-            do
-            {
-                outputCoordinates.AddRange(GetRegionPoints(currentX, centerX, currentY, centerY));
-
-                // calculate next pixel coords
-                currentX++;
-
-                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
-                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
-                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
+                foreach (var coords in ellipse)
                 {
-                    currentY--;
+                    layer.LayerBitmap.SetSRGBPixel(coords.X - layer.OffsetX, coords.Y - layer.OffsetY, color);
                 }
-
-                // calculate how far we've advanced
-                double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
-                double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
-                currentSlope = -(derivativeX / derivativeY);
             }
-            while (currentSlope > -1 && currentY - centerY > 0.5);
-
-            // from middle to 0
-            while (currentY - centerY >= 0)
+            else
             {
-                outputCoordinates.AddRange(GetRegionPoints(currentX, centerX, currentY, centerY));
-
-                currentY--;
-                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
-                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
-                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
+                using SKPaint paint = new();
+                paint.Color = color;
+                paint.BlendMode = SKBlendMode.Src;
+                float offsetX = thickness % 2 == 1 ? layer.OffsetX - 0.5f : layer.OffsetX;
+                float offsetY = thickness % 2 == 1 ? layer.OffsetY - 0.5f : layer.OffsetY;
+                foreach (var coords in ellipse)
                 {
-                    currentX++;
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawCircle(coords.X - offsetX, coords.Y - offsetY, thickness / 2f, paint);
                 }
             }
-
-            return outputCoordinates;
         }
 
-        public IEnumerable<Coordinates> CalculateFillForEllipse(IEnumerable<Coordinates> outlineCoordinates)
+        private string defaultActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
+
+        public CircleTool()
         {
-            List<Coordinates> finalCoordinates = new List<Coordinates>();
+            ActionDisplay = defaultActionDisplay;
+        }
 
-            if (!outlineCoordinates.Any())
-            {
-                return finalCoordinates;
-            }
+        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
 
-            int bottom = outlineCoordinates.Max(x => x.Y);
-            int top = outlineCoordinates.Min(x => x.Y);
-            for (int i = top + 1; i < bottom; i++)
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
-                IEnumerable<Coordinates> rowCords = outlineCoordinates.Where(x => x.Y == i);
-                int right = rowCords.Max(x => x.X);
-                int left = rowCords.Min(x => x.X);
-                for (int j = left + 1; j < right; j++)
-                {
-                    finalCoordinates.Add(new Coordinates(j, i));
-                }
+                ActionDisplay = "Click and move mouse to draw an even circle.";
             }
-
-            return finalCoordinates;
         }
 
-        private Coordinates[] FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        public override void OnKeyUp(KeyEventArgs e)
         {
-            List<Coordinates> coordinates = new List<Coordinates>();
-            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
-                coordinates.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
-                coordinates.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
+                ActionDisplay = defaultActionDisplay;
             }
-
-            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
-            {
-                coordinates.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
-                coordinates.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
-            }
-
-            return coordinates.ToArray();
         }
 
-        private Coordinates[] GetRegionPoints(double x, double xc, double y, double yc)
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
-            Coordinates[] outputCoordinates = new Coordinates[4];
-            outputCoordinates[0] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(y));
-            outputCoordinates[1] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(y));
-            outputCoordinates[2] = new Coordinates((int)Math.Floor(x), (int)Math.Floor(-(y - yc) + yc));
-            outputCoordinates[3] = new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(-(y - yc) + yc));
-            return outputCoordinates;
+            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
+            var hasFillColor = Toolbar.GetSetting<BoolSetting>("Fill").Value;
+            Color temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
+            SKColor fill = new SKColor(temp.R, temp.G, temp.B, temp.A);
+            DrawEllipseFromCoordinates(layer, coordinates[^1], coordinates[0], color, fill, thickness, hasFillColor);
         }
+
     }
-}
+}

+ 133 - 21
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -1,48 +1,160 @@
-using System.Collections.Generic;
-using System.Drawing;
-using System.Windows.Input;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Services;
 using PixiEditor.ViewModels;
-using Color = System.Windows.Media.Color;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
+using static System.Math;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class ColorPickerTool : ReadonlyTool
     {
-        public ColorPickerTool()
+        private readonly DocumentProvider _docProvider;
+        private readonly BitmapManager _bitmapManager;
+        private readonly string defaultActionDisplay = "Click to pick colors from the canvas. Hold Ctrl to pick from the reference layer. Hold Ctrl and Alt to blend the reference and canvas color";
+
+        public ColorPickerTool(DocumentProvider documentProvider, BitmapManager bitmapManager)
         {
-            ActionDisplay = "Press on pixel to make it the primary color.";
+            ActionDisplay = defaultActionDisplay;
+            _docProvider = documentProvider;
+            _bitmapManager = bitmapManager;
         }
 
         public override bool HideHighlight => true;
 
-        public override string Tooltip => "Swaps primary color with selected on canvas. (O)";
+        public override string Tooltip => "Picks the primary color from the canvas. (O)";
 
-        public override void OnMouseDown(MouseEventArgs e)
+        public override void Use(List<Coordinates> coordinates)
         {
-            base.OnMouseDown(e);
-            ViewModelMain.Current.ColorsSubViewModel.PrimaryColor = GetColorUnderMouse();
+            var coords = coordinates.First();
+            var doc = _docProvider.GetDocument();
+            if (coords.X < 0 || coords.Y < 0 || coords.X >= doc.Width || coords.Y >= doc.Height)
+                return;
+
+            ViewModelMain.Current.ColorsSubViewModel.PrimaryColor = GetColorAt(coords.X, coords.Y);
         }
 
-        public override void Use(List<Coordinates> coordinates)
+        public SKColor GetColorAt(int x, int y)
+        {
+            Layer referenceLayer = _docProvider.GetReferenceLayer();
+
+            if (referenceLayer != null && (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)))
+            {
+                double preciseX = _docProvider.GetDocument().MouseXOnCanvas;
+                double preciseY = _docProvider.GetDocument().MouseYOnCanvas;
+
+                if ((Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
+                {
+                    return GetCombinedColor(x, y, preciseX, preciseY);
+                }
+                return GetReferenceColor(preciseX, preciseY);
+            }
+
+            return GetCanvasColor(x, y);
+        }
+
+        private SKColor GetCombinedColor(int x, int y, double preciseX, double preciseY)
+        {
+            SKColor top = GetCanvasColor(x, y);
+            SKColor bottom = GetReferenceColor(preciseX, preciseY);
+            return BitmapUtils.BlendColors(bottom, top);
+        }
+
+        private SKColor GetCanvasColor(int x, int y)
+        {
+            return _docProvider.GetRenderer()?.FinalSurface.GetSRGBPixel(x, y) ?? SKColors.Transparent;
+        }
+
+        private SKColor GetReferenceColor(double x, double y)
+        {
+            Document activeDocument = _docProvider.GetDocument();
+            Layer referenceLayer = _docProvider.GetReferenceLayer();
+            Coordinates refPos = CanvasSpaceToReferenceSpace(
+                    x, y,
+                    activeDocument.Width, activeDocument.Height,
+                    referenceLayer.Width, referenceLayer.Height);
+
+            if (refPos.X >= 0 && refPos.Y >= 0 && refPos.X < referenceLayer.Width && refPos.Y < referenceLayer.Height)
+                return referenceLayer.LayerBitmap.GetSRGBPixel(refPos.X, refPos.Y);
+            return SKColors.Transparent;
+        }
+
+        private Coordinates CanvasSpaceToReferenceSpace(double canvasX, double canvasY, int canvasW, int canvasH, int referenceW, int referenceH)
+        {
+            double canvasRatio = canvasW / (double)canvasH;
+            double referenceRatio = referenceW / (double)referenceH;
+            bool blackBarsAreOnTopAndBottom = referenceRatio > canvasRatio;
+            if (blackBarsAreOnTopAndBottom)
+            {
+                double combinedBlackBarsHeight = (1 - canvasRatio / referenceRatio) * canvasH;
+                double refScale = referenceH / ((double)canvasH - combinedBlackBarsHeight);
+
+                int outX = (int)Math.Floor(canvasX * referenceW / canvasW);
+                int outY = (int)Math.Floor((canvasY - combinedBlackBarsHeight / 2) * refScale);
+
+                return new Coordinates(outX, outY);
+            }
+            else
+            {
+                double combinedBlackBarsWidth = (1 - referenceRatio / canvasRatio) * canvasW;
+                double refScale = referenceW / ((double)canvasW - combinedBlackBarsWidth);
+
+                int outX = (int)Math.Floor((canvasX - combinedBlackBarsWidth / 2) * refScale);
+                int outY = (int)Math.Floor(canvasY * referenceH / canvasH);
+                return new Coordinates(outX, outY);
+            }
+        }
+
+        public override void OnKeyDown(KeyEventArgs e)
         {
-            ViewModelMain.Current.ColorsSubViewModel.PrimaryColor = GetColorUnderMouse();
+            UpdateActionDisplay();
         }
 
-        public Color GetColorUnderMouse()
+        public override void OnKeyUp(KeyEventArgs e)
         {
-            System.Drawing.Color color;
-            using (Bitmap bitmap = new Bitmap(1, 1))
+            UpdateActionDisplay();
+        }
+
+        public override void OnSelected()
+        {
+            UpdateActionDisplay();
+        }
+
+        public override void OnDeselected()
+        {
+            _bitmapManager.OnlyReferenceLayer = false;
+            _bitmapManager.HideReferenceLayer = false;
+        }
+
+        private void UpdateActionDisplay()
+        {
+            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
             {
-                using (Graphics graphics = Graphics.FromImage(bitmap))
+                if (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
                 {
-                    graphics.CopyFromScreen(MousePositionConverter.GetCursorPosition(), new Point(0, 0), new Size(1, 1));
+                    _bitmapManager.HideReferenceLayer = false;
+                    _bitmapManager.OnlyReferenceLayer = false;
+                    ActionDisplay = "Click to pick colors from both the canvas and the reference layer blended together. Release Ctrl and Alt to pick from the canvas. Release just Alt to pick from the reference layer";
+                    return;
                 }
 
-                color = bitmap.GetPixel(0, 0);
+                _bitmapManager.HideReferenceLayer = false;
+                _bitmapManager.OnlyReferenceLayer = true;
+                ActionDisplay = "Click to pick colors from the reference layer. Release Ctrl to pick from the canvas. Hold Ctrl and Alt to blend the reference and canvas color";
+            }
+            else
+            {
+                _bitmapManager.HideReferenceLayer = true;
+                _bitmapManager.OnlyReferenceLayer = false;
+                ActionDisplay = defaultActionDisplay;
             }
-
-            return Color.FromArgb(color.A, color.R, color.G, color.B);
         }
     }
-}
+}

+ 35 - 36
PixiEditor/Models/Tools/Tools/EraserTool.cs

@@ -1,38 +1,37 @@
-using System.Collections.Generic;
-using System.Windows.Media;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class EraserTool : BitmapOperationTool
-    {
-        private readonly PenTool pen;
-
-        public EraserTool(BitmapManager bitmapManager)
-        {
-            ActionDisplay = "Draw to remove color from a pixel.";
-            Toolbar = new BasicToolbar();
-            pen = new PenTool(bitmapManager);
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using SkiaSharp;
+using System.Collections.Generic;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class EraserTool : BitmapOperationTool
+    {
+        private readonly PenTool pen;
+
+        public EraserTool(BitmapManager bitmapManager)
+        {
+            ActionDisplay = "Draw to remove color from a pixel.";
+            Toolbar = new BasicToolbar();
+            pen = new PenTool(bitmapManager);
+        }

+

+        public override bool UsesShift => false;

+

+        public override string Tooltip => "Erasers color from pixel. (E)";
+
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        {
+            Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
         }
 
-        public override bool UsesShift => false;
-		public override string Tooltip => "Erasers color from pixel. (E)";
-
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
-        {
-            return Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
-        }
-
-        public LayerChange[] Erase(Layer layer, List<Coordinates> coordinates, int toolSize)
-        {
-            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
-            BitmapPixelChanges pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
-            return Only(pixels, layer);
-        }
-    }
-}
+        public void Erase(Layer layer, List<Coordinates> coordinates, int toolSize)
+        {
+            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
+            pen.Draw(layer, startingCords, coordinates[0], SKColors.Transparent, toolSize, false, null, SKBlendMode.Src);
+        }
+    }
+}

+ 0 - 80
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -1,80 +0,0 @@
-using System.Collections.Generic;
-using System.Windows.Media;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class FloodFill : BitmapOperationTool
-    {
-        private BitmapManager BitmapManager { get; }
-
-        public FloodFill(BitmapManager bitmapManager)
-        {
-            ActionDisplay = "Press on a area to fill it.";
-            BitmapManager = bitmapManager;
-        }
-
-        public override string Tooltip => "Fills area with color. (G)";
-
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
-        {
-            return Only(ForestFire(layer, coordinates[0], color), layer);
-        }
-
-        public BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
-        {
-            List<Coordinates> changedCoords = new List<Coordinates>();
-
-            int width = BitmapManager.ActiveDocument.Width;
-            int height = BitmapManager.ActiveDocument.Height;
-
-            var visited = new bool[width, height];
-
-            Color colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
-
-            var stack = new Stack<Coordinates>();
-            stack.Push(new Coordinates(startingCoords.X, startingCoords.Y));
-
-            while (stack.Count > 0)
-            {
-                var cords = stack.Pop();
-                var relativeCords = layer.GetRelativePosition(cords);
-
-                if (cords.X < 0 || cords.X > width - 1)
-                {
-                    continue;
-                }
-
-                if (cords.Y < 0 || cords.Y > height - 1)
-                {
-                    continue;
-                }
-
-                if (visited[cords.X, cords.Y])
-                {
-                    continue;
-                }
-
-                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == newColor)
-                {
-                    continue;
-                }
-
-                if (layer.GetPixel(relativeCords.X, relativeCords.Y) == colorToReplace)
-                {
-                    changedCoords.Add(new Coordinates(cords.X, cords.Y));
-                    visited[cords.X, cords.Y] = true;
-                    stack.Push(new Coordinates(cords.X, cords.Y - 1));
-                    stack.Push(new Coordinates(cords.X, cords.Y + 1));
-                    stack.Push(new Coordinates(cords.X - 1, cords.Y));
-                    stack.Push(new Coordinates(cords.X + 1, cords.Y));
-                }
-            }
-
-            return BitmapPixelChanges.FromSingleColoredArray(changedCoords, newColor);
-        }
-    }
-}

+ 133 - 0
PixiEditor/Models/Tools/Tools/FloodFillTool.cs

@@ -0,0 +1,133 @@
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using SkiaSharp;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Windows;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class FloodFillTool : BitmapOperationTool
+    {
+        private BitmapManager BitmapManager { get; }
+        private SKPaint fillPaint = new SKPaint() { BlendMode = SKBlendMode.Src };
+
+        public FloodFillTool(BitmapManager bitmapManager)
+        {
+            ActionDisplay = "Press on an area to fill it.";
+            BitmapManager = bitmapManager;
+        }
+
+        public override string Tooltip => "Fills area with color. (G)";
+
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        {
+            if (layer.IsReset)
+            {
+                layer.DynamicResizeAbsolute(new(0, 0, BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
+                layer.LayerBitmap.SkiaSurface.Canvas.Clear(color);
+                layer.InvokeLayerBitmapChange();
+            }
+            else
+            {
+                LinearFill(layer, coordinates[0], color);
+            }
+        }
+
+        public void LinearFill(Layer layer, Coordinates startingCoords, SKColor newColor)
+        {
+            Queue<FloodFillRange> floodFillQueue = new Queue<FloodFillRange>();
+            SKColor colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
+            if ((colorToReplace.Alpha == 0 && newColor.Alpha == 0) ||
+                colorToReplace == newColor)
+                return;
+
+            int width = BitmapManager.ActiveDocument.Width;
+            int height = BitmapManager.ActiveDocument.Height;
+            if (startingCoords.X < 0 || startingCoords.Y < 0 || startingCoords.X >= width || startingCoords.Y >= height)
+                return;
+            var visited = new bool[width * height];
+
+            fillPaint.Color = newColor;
+
+            Int32Rect dirtyRect = new Int32Rect(startingCoords.X, startingCoords.Y, 1, 1);
+
+            PerformLinearFill(layer, floodFillQueue, startingCoords, width, colorToReplace, ref dirtyRect, visited);
+            PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, visited);
+
+            layer.InvokeLayerBitmapChange(dirtyRect);
+        }
+
+        private void PerformLinearFill(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            Coordinates coords, int width, SKColor colorToReplace, ref Int32Rect dirtyRect, bool[] visited)
+        {
+            // Find the Left Edge of the Color Area
+            int fillXLeft = coords.X;
+            while (true)
+            {
+                // Indicate that this pixel has been checked
+                int pixelIndex = (coords.Y * width) + fillXLeft;
+                visited[pixelIndex] = true;
+
+                // Move one pixel to the left
+                fillXLeft--;
+                // Exit the loop if we're at edge of the bitmap or the color area
+                if (fillXLeft < 0 || visited[pixelIndex - 1] || layer.GetPixelWithOffset(fillXLeft, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelLeft = fillXLeft + 1;
+
+            // Find the Right Edge of the Color Area
+            int fillXRight = coords.X;
+            while (true)
+            {
+                int pixelIndex = (coords.Y * width) + fillXRight;
+                visited[pixelIndex] = true;
+
+                fillXRight++;
+                if (fillXRight >= width || visited[pixelIndex + 1] || layer.GetPixelWithOffset(fillXRight, coords.Y) != colorToReplace)
+                    break;
+            }
+            int lastCheckedPixelRight = fillXRight - 1;
+
+            layer.DynamicResizeAbsolute(new(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));
+            int relativeY = coords.Y - layer.OffsetY;
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawLine(lastCheckedPixelLeft - layer.OffsetX, relativeY, lastCheckedPixelRight - layer.OffsetX + 1, relativeY, fillPaint);
+            dirtyRect = dirtyRect.Expand(new Int32Rect(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));
+
+            FloodFillRange range = new FloodFillRange(lastCheckedPixelLeft, lastCheckedPixelRight, coords.Y);
+            floodFillQueue.Enqueue(range);
+        }
+
+        private void PerformFloodFIll(
+            Layer layer, Queue<FloodFillRange> floodFillQueue,
+            SKColor colorToReplace, ref Int32Rect dirtyRect, int width, int height, bool[] pixelsVisited)
+        {
+            while (floodFillQueue.Count > 0)
+            {
+                FloodFillRange range = floodFillQueue.Dequeue();
+
+                //START THE LOOP UPWARDS AND DOWNWARDS
+                int upY = range.Y - 1; //so we can pass the y coord by ref
+                int downY = range.Y + 1;
+                int downPixelxIndex = (width * (range.Y + 1)) + range.StartX;
+                int upPixelIndex = (width * (range.Y - 1)) + range.StartX;
+                for (int i = range.StartX; i <= range.EndX; i++)
+                {
+                    //START LOOP UPWARDS
+                    //if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
+                    if (range.Y > 0 && (!pixelsVisited[upPixelIndex]) && layer.GetPixelWithOffset(i, upY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, upY), width, colorToReplace, ref dirtyRect, pixelsVisited);
+                    //START LOOP DOWNWARDS
+                    if (range.Y < (height - 1) && (!pixelsVisited[downPixelxIndex]) && layer.GetPixelWithOffset(i, downY) == colorToReplace)
+                        PerformLinearFill(layer, floodFillQueue, new Coordinates(i, downY), width, colorToReplace, ref dirtyRect, pixelsVisited);
+                    downPixelxIndex++;
+                    upPixelIndex++;
+                }
+            }
+        }
+    }
+}

+ 152 - 92
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -1,23 +1,123 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class LineTool : ShapeTool
     {
         private readonly CircleTool circleTool;
+        private List<Coordinates> linePoints = new List<Coordinates>();
+        private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };
+
+        public bool AutomaticallyResizeCanvas { get; set; } = true;
+
+        public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates end)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            CalculateBresenhamLine(start, end, output);
+            return output;
+        }
+
+        public static void CalculateBresenhamLine(Coordinates start, Coordinates end, List<Coordinates> output)
+        {
+            int x1 = start.X;
+            int x2 = end.X;
+            int y1 = start.Y;
+            int y2 = end.Y;
+
+            if (x1 == x2 && y1 == y2)
+            {
+                output.Add(start);
+                return;
+            }
+
+            int d, dx, dy, ai, bi, xi, yi;
+            int x = x1, y = y1;
+
+            if (x1 < x2)
+            {
+                xi = 1;
+                dx = x2 - x1;
+            }
+            else
+            {
+                xi = -1;
+                dx = x1 - x2;
+            }
+
+            if (y1 < y2)
+            {
+                yi = 1;
+                dy = y2 - y1;
+            }
+            else
+            {
+                yi = -1;
+                dy = y1 - y2;
+            }
+
+            output.Add(new Coordinates(x, y));
+
+            if (dx > dy)
+            {
+                ai = (dy - dx) * 2;
+                bi = dy * 2;
+                d = bi - dx;
+
+                while (x != x2)
+                {
+                    if (d >= 0)
+                    {
+                        x += xi;
+                        y += yi;
+                        d += ai;
+                    }
+                    else
+                    {
+                        d += bi;
+                        x += xi;
+                    }
+
+                    output.Add(new Coordinates(x, y));
+                }
+            }
+            else
+            {
+                ai = (dx - dy) * 2;
+                bi = dx * 2;
+                d = bi - dy;
+
+                while (y != y2)
+                {
+                    if (d >= 0)
+                    {
+                        x += xi;
+                        y += yi;
+                        d += ai;
+                    }
+                    else
+                    {
+                        d += bi;
+                        y += yi;
+                    }
+
+                    output.Add(new Coordinates(x, y));
+                }
+            }
+        }
+
+        private string defaltActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
 
         public LineTool()
         {
-            ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
+            ActionDisplay = defaltActionDisplay;
             Toolbar = new BasicToolbar();
             circleTool = new CircleTool();
         }
@@ -26,7 +126,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void OnKeyDown(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftShift)
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
                 ActionDisplay = "Click and move mouse to draw an even line.";
             }
@@ -34,110 +134,72 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void OnKeyUp(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftShift)
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
-                ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
+                ActionDisplay = defaltActionDisplay;
             }
         }
 
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
-            BitmapPixelChanges pixels =
-                BitmapPixelChanges.FromSingleColoredArray(
-                    CreateLine(
-                        coordinates,
-                        Toolbar.GetSetting<SizeSetting>("ToolSize").Value,
-                        CapType.Square,
-                        CapType.Square), color);
-            return Only(pixels, layer);
-        }
-
-        public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness)
-        {
-            return CreateLineFastest(start, end, thickness);
-        }
-
-        public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
-        {
-            return CreateLine(new List<Coordinates>() { end, start }, thickness, startCap, endCap);
-        }
+            int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
 
-        private IEnumerable<Coordinates> CreateLine(IEnumerable<Coordinates> coordinates, int thickness, CapType startCap, CapType endCap)
-        {
-            Coordinates startingCoordinates = coordinates.Last();
-            Coordinates latestCoordinates = coordinates.First();
-            if (thickness == 1)
-            {
-                return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X, latestCoordinates.Y);
-            }
+            Coordinates start = coordinates[0];
+            Coordinates end = coordinates[^1];
 
-            return GetLinePoints(startingCoordinates, latestCoordinates, thickness, startCap, endCap);
+            DrawLine(layer, start, end, color, thickness, SKBlendMode.Src);
         }
 
-        private IEnumerable<Coordinates> CreateLineFastest(Coordinates start, Coordinates end, int thickness)
+        public void DrawLine(
+            Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
+            SKStrokeCap strokeCap = SKStrokeCap.Butt)
         {
-            IEnumerable<Coordinates> line = BresenhamLine(start.X, start.Y, end.X, end.Y);
-            if (thickness == 1)
-            {
-                return line;
-            }
+            int x = start.X;
+            int y = start.Y;
+            int x1 = end.X;
+            int y1 = end.Y;
 
-            return GetThickShape(line, thickness);
-        }
+            int dirtyX = Math.Min(x, x1) - thickness;
+            int dirtyY = Math.Min(y, y1) - thickness;
 
-        private IEnumerable<Coordinates> GetLinePoints(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
-        {
-            IEnumerable<Coordinates> startingCap = GetCapCoordinates(startCap, start, thickness);
-            if (start == end)
+            Int32Rect dirtyRect = new Int32Rect(
+                dirtyX,
+                dirtyY,
+                Math.Max(x1, x) + thickness - dirtyX,
+                Math.Max(y1, y) + thickness - dirtyY);
+            if (AutomaticallyResizeCanvas)
             {
-                return startingCap;
+                layer.DynamicResizeAbsolute(dirtyRect);
             }
 
-            IEnumerable<Coordinates> line = BresenhamLine(start.X, start.Y, end.X, end.Y);
+            x -= layer.OffsetX;
+            y -= layer.OffsetY;
+            x1 -= layer.OffsetX;
+            y1 -= layer.OffsetY;
 
-            List<Coordinates> output = new List<Coordinates>(startingCap);
+            paint.StrokeWidth = thickness;
+            paint.Color = color;
+            paint.BlendMode = blendMode;
+            paint.StrokeCap = strokeCap;
 
-            output.AddRange(GetCapCoordinates(endCap, end, thickness));
-            if (line.Count() > 2)
+            if (thickness == 1)
             {
-                output.AddRange(GetThickShape(line.Except(new[] { start, end }), thickness));
+                DrawBresenhamLine(layer, x, y, x1, y1, paint);
             }
-
-            return output.Distinct();
-        }
-
-        private IEnumerable<Coordinates> GetCapCoordinates(CapType cap, Coordinates position, int thickness)
-        {
-            switch (cap)
+            else
             {
-                case CapType.Round:
-                    {
-                        return GetRoundCap(position, thickness); // Round cap is not working very well, circle tool must be improved
-                    }
-
-                default:
-                    return GetThickShape(new[] { position }, thickness);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawLine(x, y, x1, y1, paint);
             }
-        }
 
-        /// <summary>
-        ///     Gets points for rounded cap on specified position and thickness.
-        /// </summary>
-        /// <param name="position">Starting position of cap.</param>
-        /// <param name="thickness">Thickness of cap.</param>
-        private IEnumerable<Coordinates> GetRoundCap(Coordinates position, int thickness)
-        {
-            IEnumerable<Coordinates> rectangleCords = CoordinatesCalculator.RectangleToCoordinates(
-                CoordinatesCalculator.CalculateThicknessCenter(position, thickness));
-            return circleTool.CreateEllipse(rectangleCords.First(), rectangleCords.Last(), 1, true);
+            layer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        private IEnumerable<Coordinates> BresenhamLine(int x1, int y1, int x2, int y2)
+        private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)
         {
-            List<Coordinates> coordinates = new List<Coordinates>();
             if (x1 == x2 && y1 == y2)
             {
-                return new[] { new Coordinates(x1, y1) };
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x1, y1, paint);
+                return;
             }
 
             int d, dx, dy, ai, bi, xi, yi;
@@ -165,7 +227,7 @@ namespace PixiEditor.Models.Tools.Tools
                 dy = y1 - y2;
             }
 
-            coordinates.Add(new Coordinates(x, y));
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
 
             if (dx > dy)
             {
@@ -187,7 +249,7 @@ namespace PixiEditor.Models.Tools.Tools
                         x += xi;
                     }
 
-                    coordinates.Add(new Coordinates(x, y));
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
                 }
             }
             else
@@ -210,11 +272,9 @@ namespace PixiEditor.Models.Tools.Tools
                         y += yi;
                     }
 
-                    coordinates.Add(new Coordinates(x, y));
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(x, y, paint);
                 }
             }
-
-            return coordinates;
         }
     }
-}
+}

+ 21 - 12
PixiEditor/Models/Tools/Tools/MagicWandTool.cs

@@ -8,22 +8,22 @@ using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using PixiEditor.ViewModels;
-using System;
+using SkiaSharp;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Windows;
 using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class MagicWandTool : ReadonlyTool, ICachedDocumentTool
     {
-        private readonly FloodFill floodFill;
-
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
         private BitmapManager BitmapManager { get; }
 
         private IEnumerable<Coordinates> oldSelection;
+        private List<Coordinates> newSelection = new List<Coordinates>();
 
         public override string Tooltip => "Magic Wand (W). Flood's the selection";
 
@@ -56,20 +56,26 @@ namespace PixiEditor.Models.Tools.Tools
 
             Selection selection = BitmapManager.ActiveDocument.ActiveSelection;
 
-            selection.SetSelection(
-                floodFill.ForestFire(
-                    layer,
-                    new Coordinates((int)document.MouseXOnCanvas, (int)document.MouseYOnCanvas),
-                    System.Windows.Media.Colors.White
-                    ).ChangedPixels.Keys,
-                selectionType);
+
+            newSelection.Clear();
+
+            ToolCalculator.GetLinearFillAbsolute(
+                   layer,
+                   new Coordinates(
+                       (int)document.MouseXOnCanvas,
+                       (int)document.MouseYOnCanvas),
+                   BitmapManager.ActiveDocument.Width,
+                   BitmapManager.ActiveDocument.Height,
+                   SKColors.White,
+                   newSelection);
+
+            selection.SetSelection(newSelection, selectionType);
 
             SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelection, selectionType);
         }
 
         public MagicWandTool(BitmapManager manager)
         {
-            floodFill = new FloodFill(manager);
             BitmapManager = manager;
 
             Toolbar = new MagicWandToolbar();
@@ -88,7 +94,10 @@ namespace PixiEditor.Models.Tools.Tools
 
         private void ValidateCache(Document document)
         {
-            cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(document.Width, document.Height, document.Layers, document.LayerStructure));
+            cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(
+                new Int32Rect(0, 0, document.Width, document.Height),
+                document.Layers,
+                document.LayerStructure));
         }
     }
 }

+ 197 - 180
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -1,250 +1,267 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Helpers.Extensions;
+using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Undo;
+using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
 using Transform = PixiEditor.Models.ImageManipulation.Transform;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class MoveTool : BitmapOperationTool
     {
+        private static readonly SKPaint maskingPaint = new()
+        {
+            BlendMode = SKBlendMode.DstIn,
+        };
+
+        private static readonly SKPaint inverseMaskingPaint = new()
+        {
+            BlendMode = SKBlendMode.DstOut,
+        };
+
         private Layer[] affectedLayers;
-        private Dictionary<Guid, bool> clearedPixels = new Dictionary<Guid, bool>();
-        private Coordinates[] currentSelection;
-        private Coordinates lastMouseMove;
-        private Dictionary<Guid, Color[]> startPixelColors;
-        private Dictionary<Guid, Color[]> endPixelColors;
-        private Dictionary<Guid, Thickness> startingOffsets;
-        private Coordinates[] startSelection;
-        private bool updateViewModelSelection = true;
-
+        private Surface[] currentlyDragged;
+        private Coordinates[] currentlyDraggedPositions;
+        private Surface previewLayerData;
+
+        private List<Coordinates> moveStartSelectedPoints = null;
+        private Coordinates moveStartPos;
+        private Int32Rect moveStartRect;
+
+        private Coordinates lastDragDelta;
+
+        private StorageBasedChange change;
+
+        private string defaultActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+
         public MoveTool(BitmapManager bitmapManager)
         {
-            ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+            ActionDisplay = defaultActionDisplay;
             Cursor = Cursors.Arrow;
             RequiresPreviewLayer = true;
-            UseDefaultUndoMethod = true;
+            UseDefaultUndoMethod = false;
 
             BitmapManager = bitmapManager;
-        }
-
-        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";
-
-        public override bool HideHighlight => true;
-
-        public bool MoveAll { get; set; }
+        }
 
-        private BitmapManager BitmapManager { get; }
+        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";
 
-        public override void OnKeyDown(KeyEventArgs e)
-        {
-            if (e.Key == Key.LeftCtrl)
-            {
-                ActionDisplay = "Hold mouse to move all selected layers.";
-            }
-        }
+        public override bool HideHighlight => true;
 
-        public override void OnKeyUp(KeyEventArgs e)
-        {
-            if (e.Key == Key.LeftCtrl)
-            {
-                ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
-            }
-        }
+        private BitmapManager BitmapManager { get; }
 
-        public override void AfterAddedUndo(UndoManager undoManager)
-        {
-            if (currentSelection == null || currentSelection.Length == 0)
-            {
-                return;
-            }
-
-            Change changes = undoManager.UndoStack.Peek();
-
-            // Inject to default undo system change custom changes made by this tool
-            foreach (var item in startPixelColors)
-            {
-                BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-                BitmapPixelChanges afterMovePixels = BitmapPixelChanges.FromArrays(currentSelection, endPixelColors[item.Key]);
-                Guid layerGuid = item.Key;
-                var oldValue = (LayerChange[])changes.OldValue;
-
-                if (oldValue.Any(x => x.LayerGuid == layerGuid))
-                {
-                    var layer = oldValue.First(x => x.LayerGuid == layerGuid);
-                    layer.PixelChanges.ChangedPixels.AddRangeOverride(afterMovePixels.ChangedPixels);
-                    layer.PixelChanges.ChangedPixels
-                        .AddRangeOverride(beforeMovePixels.ChangedPixels);
-
-                    ((LayerChange[])changes.NewValue).First(x => x.LayerGuid == layerGuid).PixelChanges.ChangedPixels
-                        .AddRangeNewOnly(BitmapPixelChanges
-                            .FromSingleColoredArray(startSelection, System.Windows.Media.Colors.Transparent)
-                            .ChangedPixels);
-                }
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
+            {
+                ActionDisplay = "Hold mouse to move all layers.";
             }
         }
 
-        // This adds undo if there is no selection, reason why this isn't in AfterUndoAdded,
-        // is because it doesn't fire if no pixel changes were made.
-        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        public override void OnKeyUp(KeyEventArgs e)
         {
-            if (currentSelection != null && currentSelection.Length == 0)
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
             {
-                BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
-                    ApplyOffsets,
-                    new object[] { startingOffsets },
-                    ApplyOffsets,
-                    new object[] { GetOffsets(affectedLayers) },
-                    "Move layers"));
+                ActionDisplay = defaultActionDisplay;
             }
         }
 
-        public override void OnStart(Coordinates startPos)
-        {
-            ResetSelectionValues(startPos);
-
-            // Move offset if no selection
-            Document doc = BitmapManager.ActiveDocument;
-            Selection selection = doc.ActiveSelection;
-            if (selection != null && selection.SelectedPoints.Count > 0)
-            {
-                currentSelection = selection.SelectedPoints.ToArray();
-            }
-            else
-            {
-                currentSelection = Array.Empty<Coordinates>();
-            }
-
-            if (Keyboard.IsKeyDown(Key.LeftCtrl) || MoveAll)
-            {
-                affectedLayers = doc.Layers.Where(x => x.IsVisible)
-                    .ToArray();
-            }
-            else
-            {
-                affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
-            }
-
-            startSelection = currentSelection;
-            startPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, startSelection);
-            startingOffsets = GetOffsets(affectedLayers);
+        public override void AddUndoProcess(Document document)
+        {
+            var args = new object[] { change.Document };
+            document.UndoManager.AddUndoChange(change.ToChange(UndoProcess, args));
+            if (moveStartSelectedPoints != null)
+            {
+                SelectionHelpers.AddSelectionUndoStep(document, moveStartSelectedPoints, SelectionType.New);
+                document.UndoManager.SquashUndoChanges(3, "Move selected area");
+                moveStartSelectedPoints = null;
+            }
+            change = null;
         }
 
-        public override LayerChange[] Use(Layer layer, List<Coordinates> mouseMove, Color color)
+        private void UndoProcess(Layer[] layers, UndoLayer[] data, object[] args)
         {
-            LayerChange[] result = new LayerChange[affectedLayers.Length];
-            var end = mouseMove[0];
-            var lastSelection = currentSelection.ToArray();
-            for (int i = 0; i < affectedLayers.Length; i++)
+            if (args.Length > 0 && args[0] is Document document)
             {
-                if (currentSelection.Length > 0)
+                for (int i = 0; i < layers.Length; i++)
                 {
-                    endPixelColors = BitmapUtils.GetPixelsForSelection(affectedLayers, currentSelection);
-                    var changes = MoveSelection(affectedLayers[i], mouseMove);
-                    ClearSelectedPixels(affectedLayers[i], lastSelection);
+                    Layer layer = layers[i];
+                    document.Layers.RemoveAt(data[i].LayerIndex);
 
-                    changes = RemoveTransparentPixels(changes);
-
-                    result[i] = new LayerChange(changes, affectedLayers[i]);
-                }
-                else
-                {
-                    var vector = Transform.GetTranslation(lastMouseMove, end);
-                    affectedLayers[i].Offset = new Thickness(affectedLayers[i].OffsetX + vector.X, affectedLayers[i].OffsetY + vector.Y, 0, 0);
-                    result[i] = new LayerChange(BitmapPixelChanges.Empty, affectedLayers[i]);
+                    document.Layers.Insert(data[i].LayerIndex, layer);
+                    if (data[i].IsActive)
+                    {
+                        document.SetMainActiveLayer(data[i].LayerIndex);
+                    }
                 }
+
             }
-
-            lastMouseMove = end;
+        }
 
-            return result;
-        }
-
-        public BitmapPixelChanges MoveSelection(Layer layer, IEnumerable<Coordinates> mouseMove)
+        public override void OnStart(Coordinates startPos)
         {
-            Coordinates end = mouseMove.First();
+            Document doc = BitmapManager.ActiveDocument;
+            Selection selection = doc.ActiveSelection;
+            bool anySelection = selection.SelectedPoints.Any();
+
+            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+            {
+                affectedLayers = doc.Layers.Where(x => x.IsVisible).ToArray();
+            }
+            else
+            {
+                affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
+            }
+
+            change = new StorageBasedChange(doc, affectedLayers, true);
 
-            currentSelection = TranslateSelection(end);
-            if (updateViewModelSelection)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
+            Layer selLayer = selection.SelectionLayer;
+            moveStartRect = anySelection ? 
+                new(selLayer.OffsetX, selLayer.OffsetY, selLayer.Width, selLayer.Height) :
+                new (0, 0, doc.Width, doc.Height);
+            moveStartPos = startPos;
+            lastDragDelta = new Coordinates(0, 0);
+
+            previewLayerData?.Dispose();
+            previewLayerData = CreateCombinedPreview(anySelection ? selLayer : null, affectedLayers);
+
+            if (currentlyDragged != null)
+            {
+                foreach (var surface in currentlyDragged)
+                    surface.Dispose();
+            }
+
+            if (anySelection)
+            {
+                currentlyDragged = ExtractDraggedPortions(anySelection ? selLayer : null, affectedLayers);
+                currentlyDraggedPositions = Enumerable.Repeat(new Coordinates(selLayer.OffsetX, selLayer.OffsetY), affectedLayers.Length).ToArray();
+            }
+            else
+            {
+                (currentlyDraggedPositions, currentlyDragged) = CutDraggedLayers(affectedLayers);
             }
-
-            lastMouseMove = end;
-            return BitmapPixelChanges.FromArrays(currentSelection, startPixelColors[layer.LayerGuid]);
-        }
-
-        private void ApplyOffsets(object[] parameters)
+
+            if (anySelection)
+                moveStartSelectedPoints = selection.SelectedPoints.ToList();
+        }
+
+        private Surface CreateCombinedPreview(Layer selLayer, Layer[] layersToCombine)
         {
-            Dictionary<Guid, Thickness> offsets = (Dictionary<Guid, Thickness>)parameters[0];
-            foreach (var offset in offsets)
+            var combined = BitmapUtils.CombineLayers(moveStartRect, layersToCombine, BitmapManager.ActiveDocument.LayerStructure);
+            if (selLayer != null)
             {
-                Layer layer = ViewModelMain.Current?.BitmapManager?.
-                    ActiveDocument?.Layers?.First(x => x.LayerGuid == offset.Key);
-                layer.Offset = offset.Value;
+                using var selSnap = selLayer.LayerBitmap.SkiaSurface.Snapshot();
+                combined.SkiaSurface.Canvas.DrawImage(selSnap, 0, 0, maskingPaint);
             }
+            return combined;
         }
 
-        private Dictionary<Guid, Thickness> GetOffsets(Layer[] layers)
+        private static (Coordinates[], Surface[]) CutDraggedLayers(Layer[] draggedLayers)
         {
-            Dictionary<Guid, Thickness> dict = new Dictionary<Guid, Thickness>();
-            for (int i = 0; i < layers.Length; i++)
+            Surface[] outSurfaces = new Surface[draggedLayers.Length];
+            Coordinates[] outCoords = new Coordinates[draggedLayers.Length];
+
+            int count = 0;
+            foreach (var layer in draggedLayers)
             {
-                dict.Add(layers[i].LayerGuid, layers[i].Offset);
+                outCoords[count] = new Coordinates(layer.OffsetX, layer.OffsetY);
+                Surface copy = new(layer.Width, layer.Height);
+                layer.LayerBitmap.SkiaSurface.Draw(copy.SkiaSurface.Canvas, 0, 0, Surface.ReplacingPaint);
+                layer.LayerBitmap.SkiaSurface.Canvas.Clear();
+                layer.InvokeLayerBitmapChange();
+                outSurfaces[count] = copy;
+                count++;
             }
 
-            return dict;
+            return (outCoords, outSurfaces);
         }
 
-        private BitmapPixelChanges RemoveTransparentPixels(BitmapPixelChanges pixels)
+        private static Surface[] ExtractDraggedPortions(Layer selLayer, Layer[] draggedLayers)
         {
-            foreach (var item in pixels.ChangedPixels.Where(x => x.Value.A == 0).ToList())
-            {
-                pixels.ChangedPixels.Remove(item.Key);
-            }
-
-            return pixels;
+            using var selSnap = selLayer.LayerBitmap.SkiaSurface.Snapshot();
+            Surface[] output = new Surface[draggedLayers.Length];
+            
+            int count = 0;
+            foreach (Layer layer in draggedLayers)
+            {
+                Surface portion = new Surface(selLayer.Width, selLayer.Height);
+                SKRect selLayerRect = new SKRect(0, 0, selLayer.Width, selLayer.Height);
+
+                int x = selLayer.OffsetX - layer.OffsetX;
+                int y = selLayer.OffsetY - layer.OffsetY;
+
+                using (var layerSnap = layer.LayerBitmap.SkiaSurface.Snapshot())
+                    portion.SkiaSurface.Canvas.DrawImage(layerSnap, new SKRect(x, y, x + selLayer.Width, y + selLayer.Height), selLayerRect, Surface.ReplacingPaint);
+                portion.SkiaSurface.Canvas.DrawImage(selSnap, 0, 0, maskingPaint);
+                output[count] = portion;
+                count++;
+
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawImage(selSnap, new SKRect(0, 0, selLayer.Width, selLayer.Height), 
+                    new SKRect(selLayer.OffsetX - layer.OffsetX, selLayer.OffsetY - layer.OffsetY, selLayer.OffsetX - layer.OffsetX + selLayer.Width, selLayer.OffsetY - layer.OffsetY + selLayer.Height), 
+                    inverseMaskingPaint);
+                layer.InvokeLayerBitmapChange(new Int32Rect(selLayer.OffsetX, selLayer.OffsetY, selLayer.Width, selLayer.Height));
+            }
+            return output;
         }
 
-        private void ResetSelectionValues(Coordinates start)
+        public override void Use(Layer layer, List<Coordinates> mouseMove, SKColor color)
         {
-            lastMouseMove = start;
-            clearedPixels = new Dictionary<Guid, bool>();
-            endPixelColors = new Dictionary<Guid, Color[]>();
-            currentSelection = null;
-            affectedLayers = null;
-            updateViewModelSelection = true;
-            startPixelColors = null;
-            startSelection = null;
+            Coordinates newPos = mouseMove[0];
+            int dX = newPos.X - moveStartPos.X;
+            int dY = newPos.Y - moveStartPos.Y;
+            BitmapManager.ActiveDocument.ActiveSelection.TranslateSelection(dX - lastDragDelta.X, dY - lastDragDelta.Y);
+            lastDragDelta = new Coordinates(dX, dY);
+
+
+            int newX = moveStartRect.X + dX;
+            int newY = moveStartRect.Y + dY;
+
+            Int32Rect dirtyRect = new Int32Rect(newX, newY, moveStartRect.Width, moveStartRect.Height);
+            layer.DynamicResizeAbsolute(dirtyRect);
+            previewLayerData.SkiaSurface.Draw(layer.LayerBitmap.SkiaSurface.Canvas, newX - layer.OffsetX, newY - layer.OffsetY, Surface.ReplacingPaint);
+            layer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        private Coordinates[] TranslateSelection(Coordinates end)
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
-            Coordinates translation = Transform.GetTranslation(lastMouseMove, end);
-            return Transform.Translate(currentSelection, translation);
+            base.OnStoppedRecordingMouseUp(e);
+
+            BitmapManager.ActiveDocument.PreviewLayer.ClearCanvas();
+
+            ApplySurfacesToLayers(currentlyDragged, currentlyDraggedPositions, affectedLayers, new Coordinates(lastDragDelta.X, lastDragDelta.Y));
+            foreach (var surface in currentlyDragged)
+                surface.Dispose();
+            currentlyDragged = null;
         }
 
-        private void ClearSelectedPixels(Layer layer, Coordinates[] selection)
+        private static void ApplySurfacesToLayers(Surface[] surfaces, Coordinates[] startPositions, Layer[] layers, Coordinates delta)
         {
-            Guid layerGuid = layer.LayerGuid;
-            if (!clearedPixels.ContainsKey(layerGuid) || clearedPixels[layerGuid] == false)
+            int count = 0;
+            foreach (Surface surface in surfaces)
             {
-                BitmapManager.ActiveDocument.Layers.First(x => x == layer)
-                    .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
+                var layer = layers[count];
+                using SKImage snapshot = surface.SkiaSurface.Snapshot();
+                Coordinates position = new Coordinates(startPositions[count].X + delta.X, startPositions[count].Y + delta.Y);
+                Int32Rect dirtyRect = new Int32Rect(position.X, position.Y, surface.Width, surface.Height);
+                layer.DynamicResizeAbsolute(dirtyRect);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawImage(snapshot, position.X - layer.OffsetX, position.Y - layer.OffsetY);
+                layer.InvokeLayerBitmapChange(dirtyRect);
 
-                clearedPixels[layerGuid] = true;
+                count++;
             }
         }
     }
-}
+}

+ 199 - 150
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -1,151 +1,200 @@
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class PenTool : ShapeTool
-    {
-        private readonly SizeSetting toolSizeSetting;
-        private readonly BoolSetting pixelPerfectSetting;
-        private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
-        private readonly LineTool lineTool;
-        private Coordinates[] lastChangedPixels = new Coordinates[3];
-        private byte changedPixelsindex;
-
-        private BitmapManager BitmapManager { get; }
-
-        public PenTool(BitmapManager bitmapManager)
-        {
-            Cursor = Cursors.Pen;
-            ActionDisplay = "Click and move to draw.";
-            Toolbar = new PenToolbar();
-            toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
-            pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
-            pixelPerfectSetting.ValueChanged += PixelPerfectSettingValueChanged;
-            ClearPreviewLayerOnEachIteration = false;
-            BitmapManager = bitmapManager;
-            lineTool = new LineTool();
-        }
-
-        public override string Tooltip => "Standard brush. (B)";
-
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
-        {
-            base.OnRecordingLeftMouseDown(e);
-            changedPixelsindex = 0;
-            lastChangedPixels = new Coordinates[3];
-            confirmedPixels.Clear();
-        }
-
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
-        {
-            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
-            BitmapPixelChanges pixels = Draw(
-                startingCords,
-                coordinates[0],
-                color,
-                toolSizeSetting.Value,
-                pixelPerfectSetting.Value,
-                BitmapManager.ActiveDocument.PreviewLayer);
-            return Only(pixels, layer);
-        }
-
-        public BitmapPixelChanges Draw(Coordinates startingCoords, Coordinates latestCords, Color color, int toolSize, bool pixelPerfect = false, Layer previewLayer = null)
-        {
-            if (!pixelPerfect)
-            {
-                return BitmapPixelChanges.FromSingleColoredArray(
-                    lineTool.CreateLine(startingCoords, latestCords, toolSize), color);
-            }
-
-            if (previewLayer != null && previewLayer.GetPixelWithOffset(latestCords.X, latestCords.Y).A > 0)
-            {
-                confirmedPixels.Add(latestCords);
-            }
-
-            var latestPixels = lineTool.CreateLine(startingCoords, latestCords, 1);
-            SetPixelToCheck(latestPixels);
-
-            if (changedPixelsindex == 2)
-            {
-                var changes = ApplyPixelPerfectToPixels(
-                    lastChangedPixels[0],
-                    lastChangedPixels[1],
-                    lastChangedPixels[2],
-                    color,
-                    toolSize);
-
-                MovePixelsToCheck(changes);
-
-                changes.ChangedPixels.AddRangeNewOnly(
-                    BitmapPixelChanges.FromSingleColoredArray(GetThickShape(latestPixels, toolSize), color).ChangedPixels);
-
-                return changes;
-            }
-
-            changedPixelsindex += changedPixelsindex >= 2 ? (byte)0 : (byte)1;
-
-            var result = BitmapPixelChanges.FromSingleColoredArray(GetThickShape(latestPixels, toolSize), color);
-
-            return result;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Brushes;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class PenTool : ShapeTool
+    {
+        public Brush Brush { get; set; }
+        public List<Brush> Brushes { get; } = new List<Brush>();
+
+
+        private readonly SizeSetting toolSizeSetting;
+        private readonly BoolSetting pixelPerfectSetting;
+        private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
+        private readonly LineTool lineTool;
+        private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };
+        private Coordinates[] lastChangedPixels = new Coordinates[3];
+        private byte changedPixelsindex;
+        private Coordinates lastChangedPixel = new Coordinates(-1, -1);
+
+        private BitmapManager BitmapManager { get; }
+
+
+        public PenTool(BitmapManager bitmapManager)
+        {
+            Cursor = Cursors.Pen;
+            ActionDisplay = "Click and move to draw.";
+            Toolbar = new PenToolbar();
+            toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
+            pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
+            ClearPreviewLayerOnEachIteration = false;
+            BitmapManager = bitmapManager;
+            paint.BlendMode = SKBlendMode.Src;
+            Brushes.Add(new CircleBrush());
+            Brush = Brushes[0];
+            lineTool = new LineTool
+            {
+                AutomaticallyResizeCanvas = AutomaticallyResizeCanvas
+            };
         }
-		public override bool UsesShift => false;
-		private void MovePixelsToCheck(BitmapPixelChanges changes)
-        {
-            if (changes.ChangedPixels[lastChangedPixels[1]].A != 0)
-            {
-                lastChangedPixels[0] = lastChangedPixels[1];
-                lastChangedPixels[1] = lastChangedPixels[2];
-                changedPixelsindex = 2;
-            }
-            else
-            {
-                lastChangedPixels[0] = lastChangedPixels[2];
-                changedPixelsindex = 1;
-            }
-        }
-
-        private void SetPixelToCheck(IEnumerable<Coordinates> latestPixels)
-        {
-            if (latestPixels.Count() == 1)
-            {
-                lastChangedPixels[changedPixelsindex] = latestPixels.First();
-            }
-            else
-            {
-                lastChangedPixels[changedPixelsindex] = latestPixels.ElementAt(1);
-            }
-        }
-
-        private BitmapPixelChanges ApplyPixelPerfectToPixels(Coordinates p1, Coordinates p2, Coordinates p3, Color color, int toolSize)
-        {
-            if (Math.Abs(p3.X - p1.X) == 1 && Math.Abs(p3.Y - p1.Y) == 1 && !confirmedPixels.Contains(p2))
-            {
-                var changes = BitmapPixelChanges.FromSingleColoredArray(GetThickShape(new Coordinates[] { p1, p3 }, toolSize), color);
-                changes.ChangedPixels.AddRangeNewOnly(
-                    BitmapPixelChanges.FromSingleColoredArray(
-                        GetThickShape(new[] { p2 }, toolSize),
-                        System.Windows.Media.Colors.Transparent).ChangedPixels);
-                return changes;
-            }
-
-            return BitmapPixelChanges.FromSingleColoredArray(GetThickShape(new Coordinates[] { p2, p3 }.Distinct(), toolSize), color);
-        }
-
-        private void PixelPerfectSettingValueChanged(object sender, SettingValueChangedEventArgs<bool> e)
-        {
-            RequiresPreviewLayer = e.NewValue;
-        }
-    }
-}
+
+        public override string Tooltip => "Standard brush. (B)";
+        public override bool UsesShift => false;
+
+        public bool AutomaticallyResizeCanvas { get; set; } = true;
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            base.OnRecordingLeftMouseDown(e);
+            changedPixelsindex = 0;
+            lastChangedPixels = new Coordinates[3];
+            confirmedPixels.Clear();
+        }
+
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
+        {
+            Coordinates startingCords = coordinates.Count > 1 ? coordinates[1] : coordinates[0];
+            paint.Color = color;
+            if (AutomaticallyResizeCanvas)
+            {
+                int maxX = coordinates.Max(x => x.X);
+                int maxY = coordinates.Max(x => x.Y);
+                int minX = coordinates.Min(x => x.X);
+                int minY = coordinates.Min(x => x.Y);
+                layer.DynamicResizeAbsolute(new(minX, minY, maxX - minX + 1, maxX - minX + 1));
+            }
+            Draw(
+                layer,
+                startingCords,
+                coordinates[0],
+                color,
+                toolSizeSetting.Value,
+                pixelPerfectSetting.Value,
+                BitmapManager.ActiveDocument.PreviewLayer);
+        }
+
+        public void Draw(
+            Layer layer, Coordinates startingCoords, Coordinates latestCords, SKColor color, int toolSize,
+            bool pixelPerfect = false,
+            Layer previewLayer = null,
+            SKBlendMode blendMode = SKBlendMode.Src)
+        {
+
+            SKStrokeCap cap = SKStrokeCap.Butt;
+            paint.Color = color;
+
+            if (!pixelPerfect)
+            {
+                Brush.Draw(layer, toolSize, latestCords, paint);
+                lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
+                return;
+            }
+
+            if (latestCords != lastChangedPixel)
+            {
+                if (previewLayer != null && previewLayer.GetPixelWithOffset(latestCords.X, latestCords.Y).Alpha > 0)
+                {
+                    confirmedPixels.Add(latestCords);
+                }
+
+                Brush.Draw(layer, toolSize, latestCords, paint);
+
+                lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
+                SetPixelToCheck(LineTool.GetBresenhamLine(startingCoords, latestCords));
+
+                if (changedPixelsindex == 2)
+                {
+                    byte alpha = ApplyPixelPerfectToPixels(
+                        layer,
+                        lastChangedPixels[0],
+                        lastChangedPixels[1],
+                        lastChangedPixels[2],
+                        color,
+                        toolSize,
+                        paint);
+
+                    MovePixelsToCheck(alpha);
+
+                    lastChangedPixel = latestCords;
+                    return;
+                }
+
+                changedPixelsindex += changedPixelsindex >= 2 ? (byte)0 : (byte)1;
+            }
+
+            lastChangedPixel = latestCords;
+        }
+
+        private void MovePixelsToCheck(byte alpha)
+        {
+            if (alpha != 0)
+            {
+                lastChangedPixels[0] = lastChangedPixels[1];
+                lastChangedPixels[1] = lastChangedPixels[2];
+                changedPixelsindex = 2;
+            }
+            else
+            {
+                lastChangedPixels[0] = lastChangedPixels[2];
+                changedPixelsindex = 1;
+            }
+        }
+
+        private void SetPixelToCheck(IEnumerable<Coordinates> latestPixels)
+        {
+            if (latestPixels.Count() == 1)
+            {
+                lastChangedPixels[changedPixelsindex] = latestPixels.First();
+            }
+            else
+            {
+                lastChangedPixels[changedPixelsindex] = latestPixels.ElementAt(1);
+            }
+        }
+
+        private byte ApplyPixelPerfectToPixels(Layer layer, Coordinates p1, Coordinates p2, Coordinates p3, SKColor color, int toolSize, SKPaint paint)
+        {
+            byte alpha = color.Alpha;
+            paint.StrokeWidth = toolSize;
+
+            if (Math.Abs(p3.X - p1.X) == 1 && Math.Abs(p3.Y - p1.Y) == 1 && !confirmedPixels.Contains(p2))
+            {
+                paint.Color = SKColors.Transparent;
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(p2.X - layer.OffsetX, p2.Y - layer.OffsetY, paint);
+                paint.Color = color;
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(p1.X - layer.OffsetX, p1.Y - layer.OffsetY, paint);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(p3.X - layer.OffsetX, p3.Y - layer.OffsetY, paint);
+
+                if (lastChangedPixels.Length > 1 && p2 == lastChangedPixels[1] /*Here might be a bug, I don't remember if it should be p2*/)
+                {
+                    alpha = 0;
+                }
+            }
+            else
+            {
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(p2.X - layer.OffsetX, p2.Y - layer.OffsetY, paint);
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawPoint(p3.X - layer.OffsetX, p3.Y - layer.OffsetY, paint);
+            }
+
+            Int32Rect dirtyRect = new Int32Rect(
+               p2.X,
+               p2.Y,
+               2,
+               2);
+
+            layer.InvokeLayerBitmapChange(dirtyRect);
+            return alpha;
+        }
+    }
+}

+ 45 - 92
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -1,30 +1,29 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
-using System.Windows.Media;
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Layers;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class RectangleTool : ShapeTool
     {
+        private string defaultActionDisplay = "Click and move to draw a rectangle. Hold Shift to draw a square.";
         public RectangleTool()
         {
-            ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
+            ActionDisplay = defaultActionDisplay;
         }
 
-        public override string Tooltip => "Draws rectangle on canvas (R). Hold Shift to draw square.";
+        public override string Tooltip => "Draws rectangle on canvas (R). Hold Shift to draw a square.";
 
         public bool Filled { get; set; } = false;
 
         public override void OnKeyDown(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftShift)
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
                 ActionDisplay = "Click and move to draw a square.";
             }
@@ -32,108 +31,62 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void OnKeyUp(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftShift)
+            if (e.Key is Key.LeftShift or Key.RightShift)
             {
-                ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
+                ActionDisplay = defaultActionDisplay;
             }
         }
 
-        public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
+        public override void Use(Layer layer, List<Coordinates> coordinates, SKColor color)
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
-            BitmapPixelChanges pixels =
-                BitmapPixelChanges.FromSingleColoredArray(CreateRectangle(coordinates, thickness), color);
+            SKColor? fillColor = null;
             if (Toolbar.GetSetting<BoolSetting>("Fill").Value)
             {
-                Color fillColor = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
-                pixels.ChangedPixels.AddRangeOverride(
-                    BitmapPixelChanges.FromSingleColoredArray(
-                            CalculateFillForRectangle(coordinates[^1], coordinates[0], thickness), fillColor)
-                        .ChangedPixels);
+                var temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
+                fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
             }
-
-            return new[] { new LayerChange(pixels, layer) };
+            CreateRectangle(layer, color, fillColor, coordinates, thickness);
         }
 
-        public IEnumerable<Coordinates> CreateRectangle(List<Coordinates> coordinates, int thickness)
+        public void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, List<Coordinates> coordinates, int thickness)
         {
-            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
-            List<Coordinates> output = new List<Coordinates>();
-            IEnumerable<Coordinates> rectangle = CalculateRectanglePoints(fixedCoordinates);
-            output.AddRange(rectangle);
+            DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
 
-            for (int i = 1; i < (int)Math.Floor(thickness / 2f) + 1; i++)
-            {
-                output.AddRange(CalculateRectanglePoints(new DoubleCords(
-                    new Coordinates(fixedCoordinates.Coords1.X - i, fixedCoordinates.Coords1.Y - i),
-                    new Coordinates(fixedCoordinates.Coords2.X + i, fixedCoordinates.Coords2.Y + i))));
-            }
+            int halfThickness = (int)Math.Ceiling(thickness / 2.0);
+            Int32Rect dirtyRect = new Int32Rect(
+                fixedCoordinates.Coords1.X - halfThickness,
+                fixedCoordinates.Coords1.Y - halfThickness,
+                fixedCoordinates.Coords2.X + halfThickness * 2 - fixedCoordinates.Coords1.X,
+                fixedCoordinates.Coords2.Y + halfThickness * 2 - fixedCoordinates.Coords1.Y);
+            layer.DynamicResizeAbsolute(dirtyRect);
 
-            for (int i = 1; i < (int)Math.Ceiling(thickness / 2f); i++)
+            using (SKPaint paint = new SKPaint())
             {
-                output.AddRange(CalculateRectanglePoints(new DoubleCords(
-                    new Coordinates(fixedCoordinates.Coords1.X + i, fixedCoordinates.Coords1.Y + i),
-                    new Coordinates(fixedCoordinates.Coords2.X - i, fixedCoordinates.Coords2.Y - i))));
-            }
+                int x = fixedCoordinates.Coords1.X - layer.OffsetX;
+                int y = fixedCoordinates.Coords1.Y - layer.OffsetY;
+                int w = fixedCoordinates.Coords2.X - fixedCoordinates.Coords1.X;
+                int h = fixedCoordinates.Coords2.Y - fixedCoordinates.Coords1.Y;
+                paint.BlendMode = SKBlendMode.Src;
 
-            return output.Distinct();
-        }
-
-        public IEnumerable<Coordinates> CreateRectangle(Coordinates start, Coordinates end, int thickness)
-        {
-            return CreateRectangle(new() { end, start }, thickness);
-        }
-
-        public IEnumerable<Coordinates> CalculateFillForRectangle(Coordinates start, Coordinates end, int thickness)
-        {
-            int offset = (int)Math.Ceiling(thickness / 2f);
-            DoubleCords fixedCords = CalculateCoordinatesForShapeRotation(start, end);
-
-            DoubleCords innerCords = new DoubleCords
-            {
-                Coords1 = new Coordinates(fixedCords.Coords1.X + offset, fixedCords.Coords1.Y + offset),
-                Coords2 = new Coordinates(fixedCords.Coords2.X - (offset - 1), fixedCords.Coords2.Y - (offset - 1))
-            };
-
-            int height = innerCords.Coords2.Y - innerCords.Coords1.Y;
-            int width = innerCords.Coords2.X - innerCords.Coords1.X;
-
-            if (height < 1 || width < 1)
-            {
-                return Array.Empty<Coordinates>();
-            }
-
-            Coordinates[] filledCoordinates = new Coordinates[width * height];
-            int i = 0;
-            for (int y = 0; y < height; y++)
-            {
-                for (int x = 0; x < width; x++)
+                if (fillColor.HasValue)
                 {
-                    filledCoordinates[i] = new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y);
-                    i++;
+                    paint.Color = fillColor.Value;
+                    paint.Style = SKPaintStyle.StrokeAndFill;
+                    layer.LayerBitmap.SkiaSurface.Canvas.DrawRect(x, y, w, h, paint);
                 }
-            }
 
-            return filledCoordinates.Distinct();
+                paint.StrokeWidth = thickness;
+                paint.Style = SKPaintStyle.Stroke;
+                paint.Color = color;
+                layer.LayerBitmap.SkiaSurface.Canvas.DrawRect(x, y, w, h, paint);
+            }
+            layer.InvokeLayerBitmapChange(dirtyRect);
         }
 
-        private IEnumerable<Coordinates> CalculateRectanglePoints(DoubleCords coordinates)
+        public void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, Coordinates start, Coordinates end, int thickness)
         {
-            List<Coordinates> finalCoordinates = new List<Coordinates>();
-
-            for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
-            {
-                finalCoordinates.Add(new Coordinates(i, coordinates.Coords1.Y));
-                finalCoordinates.Add(new Coordinates(i, coordinates.Coords2.Y));
-            }
-
-            for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
-            {
-                finalCoordinates.Add(new Coordinates(coordinates.Coords1.X, i));
-                finalCoordinates.Add(new Coordinates(coordinates.Coords2.X, i));
-            }
-
-            return finalCoordinates;
+            CreateRectangle(layer, color, fillColor, new() { end, start }, thickness);
         }
     }
-}
+}

+ 17 - 8
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Diagnostics;
 using System.Linq;
 using System.Windows.Controls;
 using System.Windows.Input;
@@ -9,6 +10,7 @@ using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
@@ -66,17 +68,18 @@ namespace PixiEditor.Models.Tools.Tools
 
         public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
         {
-            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
-            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
-            return selection;
+            List<Coordinates> result = new List<Coordinates>();
+            ToolCalculator.GenerateRectangleNonAlloc(
+                start, end, true, 1, result);
+            return result;
         }
 
         public IEnumerable<Coordinates> GetCircleSelectionForPoints(Coordinates start, Coordinates end)
         {
-            DoubleCords fixedCoordinates = ShapeTool.CalculateCoordinatesForShapeRotation(start, end);
-            List<Coordinates> selection = circleTool.CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, 1).ToList();
-            selection.AddRange(circleTool.CalculateFillForEllipse(selection));
-            return selection;
+            List<Coordinates> result = new List<Coordinates>();
+            ToolCalculator.GenerateEllipseNonAlloc(
+                start, end, true, result);
+            return result;
         }
 
         /// <summary>
@@ -101,7 +104,13 @@ namespace PixiEditor.Models.Tools.Tools
         {
             IEnumerable<Coordinates> selection;
 
-            if (shape == SelectionShape.Circle)
+            BitmapManager.ActiveDocument.ActiveSelection.SetSelection(oldSelectedPoints, SelectionType.New);
+
+            if (pixels.Count < 2)
+            {
+                selection = new List<Coordinates>();
+            }
+            else if (shape == SelectionShape.Circle)
             {
                 selection = GetCircleSelectionForPoints(pixels[^1], pixels[0]);
             }

+ 5 - 4
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -8,11 +8,12 @@ namespace PixiEditor.Models.Tools.Tools
     public class ZoomTool : ReadonlyTool
     {
         private BitmapManager BitmapManager { get; }
+        private string defaultActionDisplay = "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.";
 
         public ZoomTool(BitmapManager bitmapManager)
         {
             CanStartOutsideCanvas = true;
-            ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
+            ActionDisplay = defaultActionDisplay;
             BitmapManager = bitmapManager;
         }
 
@@ -22,7 +23,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void OnKeyDown(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftCtrl)
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
             {
                 ActionDisplay = "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.";
             }
@@ -30,9 +31,9 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void OnKeyUp(KeyEventArgs e)
         {
-            if (e.Key == Key.LeftCtrl)
+            if (e.Key is Key.LeftCtrl or Key.RightCtrl)
             {
-                ActionDisplay = "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.";
+                ActionDisplay = defaultActionDisplay;
             }
         }
 

+ 13 - 1
PixiEditor/Models/Undo/Change.cs

@@ -1,9 +1,10 @@
 using System;
+using System.Linq;
 
 namespace PixiEditor.Models.Undo
 {
     [Serializable]
-    public class Change
+    public class Change : IDisposable
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="Change"/> class.
@@ -123,5 +124,16 @@ namespace PixiEditor.Models.Undo
         public Func<object[], object> FindRootProcess { get; set; }
 
         public object[] FindRootProcessArgs { get; set; }
+
+        public Action<object[], object[]> DisposeProcess { get; set; }
+
+        public void Dispose()
+        {
+            DisposeProcess?.Invoke(
+                ReverseProcessArguments ?? Array.Empty<object>(),
+                ProcessArguments ?? Array.Empty<object>());
+
+            GC.SuppressFinalize(this);
+        }
     }
 }

+ 57 - 15
PixiEditor/Models/Undo/StorageBasedChange.cs

@@ -20,14 +20,14 @@ namespace PixiEditor.Models.Undo
 
         public UndoLayer[] StoredLayers { get; set; }
 
-        private IEnumerable<Guid> layersToStore;
+        private List<Guid> layersToStore;
 
-        private Document document;
+        public Document Document { get; }
 
         public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool saveOnStartup = true)
         {
-            document = doc;
-            layersToStore = layers.Select(x => x.LayerGuid);
+            Document = doc;
+            layersToStore = layers.Select(x => x.LayerGuid).ToList();
             UndoChangeLocation = DefaultUndoChangeLocation;
             GenerateUndoLayers();
             if (saveOnStartup)
@@ -38,8 +38,8 @@ namespace PixiEditor.Models.Undo
 
         public StorageBasedChange(Document doc, IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup = true)
         {
-            document = doc;
-            layersToStore = layers.Select(x => x.LayerGuid);
+            Document = doc;
+            layersToStore = layers.Select(x => x.LayerGuid).ToList();
             UndoChangeLocation = undoChangeLocation;
             GenerateUndoLayers();
 
@@ -54,17 +54,17 @@ namespace PixiEditor.Models.Undo
             int i = 0;
             foreach (var layerGuid in layersToStore)
             {
-                Layer layer = document.Layers.First(x => x.LayerGuid == layerGuid);
+                Layer layer = Document.Layers.First(x => x.LayerGuid == layerGuid);
                 UndoLayer storedLayer = StoredLayers[i];
                 if (Directory.Exists(Path.GetDirectoryName(storedLayer.StoredPngLayerName)))
                 {
-                    Exporter.SaveAsPng(storedLayer.StoredPngLayerName, storedLayer.Width, storedLayer.Height, layer.LayerBitmap);
+                    Exporter.SaveAsGZippedBytes(storedLayer.StoredPngLayerName, layer.LayerBitmap);
                 }
 
                 i++;
             }
 
-            layersToStore = Array.Empty<Guid>();
+            layersToStore = new List<Guid>();
         }
 
         /// <summary>
@@ -77,7 +77,7 @@ namespace PixiEditor.Models.Undo
             for (int i = 0; i < StoredLayers.Length; i++)
             {
                 UndoLayer storedLayer = StoredLayers[i];
-                var bitmap = Importer.ImportImage(storedLayer.StoredPngLayerName, storedLayer.Width, storedLayer.Height);
+                var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
                 layers[i] = new Layer(storedLayer.Name, bitmap)
                 {
                     Offset = new System.Windows.Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),
@@ -95,7 +95,7 @@ namespace PixiEditor.Models.Undo
                 File.Delete(StoredLayers[i].StoredPngLayerName);
             }
 
-            layersToStore = layers.Select(x => x.LayerGuid);
+            layersToStore = layers.Select(x => x.LayerGuid).ToList();
             return layers;
         }
 
@@ -125,6 +125,29 @@ namespace PixiEditor.Models.Undo
             return new Change(finalUndoProcess, processArgs, finalRedoProcess, redoProcessParameters, description);
         }
 
+        /// <summary>
+        ///     Creates UndoManager ready Change instance, where undo and redo is the same, before process images are loaded from disk and current ones are saved.
+        /// </summary>
+        /// <param name="undoRedoProcess">Process that is invoked on redo and undo.</param>
+        /// <param name="processArgs">Custom parameters for undo and redo process.</param>
+        /// <param name="description">Undo change description.</param>
+        /// <returns>UndoManager ready Change instance.</returns>
+        public Change ToChange(Action<Layer[], UndoLayer[], object[]> undoRedoProcess, object[] processArgs, string description = "")
+        {
+            Action<object[]> finalProcess = processParameters =>
+            {
+
+                Layer[] layers = LoadLayersFromDevice();
+                GenerateUndoLayers();
+
+                SaveLayersOnDevice();
+
+                undoRedoProcess(layers, StoredLayers, processParameters);
+            };
+
+            return new Change(finalProcess, processArgs, finalProcess, processArgs, description);
+        }
+
         /// <summary>
         ///     Creates UndoManager ready Change instance, where undo process loads layers from device, and redo saves them.
         /// </summary>
@@ -206,19 +229,19 @@ namespace PixiEditor.Models.Undo
         /// </summary>
         private void GenerateUndoLayers()
         {
-            StoredLayers = new UndoLayer[layersToStore.Count()];
+            StoredLayers = new UndoLayer[layersToStore.Count];
             int i = 0;
             foreach (var layerGuid in layersToStore)
             {
-                Layer layer = document.Layers.First(x => x.LayerGuid == layerGuid);
-                if (!document.Layers.Contains(layer))
+                Layer layer = Document.Layers.First(x => x.LayerGuid == layerGuid);
+                if (!Document.Layers.Contains(layer))
                 {
                     throw new ArgumentException("Provided document doesn't contain selected layer");
                 }
 
                 layer.ClipCanvas();
 
-                int index = document.Layers.IndexOf(layer);
+                int index = Document.Layers.IndexOf(layer);
                 string pngName = layer.Name + Guid.NewGuid().ToString();
                 StoredLayers[i] = new UndoLayer(
                     Path.Join(
@@ -229,5 +252,24 @@ namespace PixiEditor.Models.Undo
                 i++;
             }
         }
+
+        public static void BasicUndoProcess(Layer[] layers, UndoLayer[] data, object[] args)
+        {
+            if (args.Length > 0 && args[0] is Document document)
+            {
+                for (int i = 0; i < layers.Length; i++)
+                {
+                    Layer layer = layers[i];
+                    document.Layers.RemoveAt(data[i].LayerIndex);
+
+                    document.Layers.Insert(data[i].LayerIndex, layer);
+                    if (data[i].IsActive)
+                    {
+                        document.SetMainActiveLayer(data[i].LayerIndex);
+                    }
+                }
+
+            }
+        }
     }
 }

+ 37 - 5
PixiEditor/PixiEditor.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
-    <TargetFramework>net5.0-windows</TargetFramework>
+    <TargetFramework>net6.0-windows10.0.22000.0</TargetFramework>
     <UseWPF>true</UseWPF>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
@@ -14,8 +14,9 @@
     <PackageIcon>icon.ico</PackageIcon>
     <ApplicationIcon>..\icon.ico</ApplicationIcon>
     <Authors>Krzysztof Krysiński, Egor Mozgovoy, CPK</Authors>
-    <Configurations>Debug;Release;MSIX;MSIX Debug</Configurations>
+    <Configurations>Debug;Release;MSIX;MSIX Debug;Dev Release</Configurations>
     <Platforms>AnyCPU;x64;x86</Platforms>
+    <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='MSIX|AnyCPU'">
@@ -35,16 +36,34 @@
     <DefineConstants>TRACE;UPDATE</DefineConstants>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev Release|AnyCPU'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DefineConstants>TRACE;UPDATE</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DefineConstants>TRACE;UPDATE</DefineConstants>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev Release|x86'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DefineConstants>TRACE;UPDATE</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DefineConstants>TRACE;UPDATE</DefineConstants>
   </PropertyGroup>
 
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Dev Release|x64'">
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <DefineConstants>TRACE;UPDATE</DefineConstants>
+    <Optimize>True</Optimize>
+  </PropertyGroup>
+
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='MSIX|AnyCPU'">
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DefineConstants>TRACE;RELEASE</DefineConstants>
@@ -67,6 +86,7 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DebugType>full</DebugType>
     <DebugSymbols>true</DebugSymbols>
+    <WarningLevel>0</WarningLevel>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
@@ -162,19 +182,23 @@
     </None>
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Dirkster.AvalonDock" Version="4.51.1" />
+    <PackageReference Include="Dirkster.AvalonDock" Version="4.60.0" />
     <PackageReference Include="DiscordRichPresence" Version="1.0.175" />
     <PackageReference Include="Expression.Blend.Sdk">
       <Version>1.0.2</Version>
       <NoWarn>NU1701</NoWarn>
     </PackageReference>
-    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+    <PackageReference Include="MessagePack" Version="2.3.85" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1">
       <NoWarn>NU1701</NoWarn>
     </PackageReference>
     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
     <PackageReference Include="PixiEditor.ColorPicker" Version="3.1.0" />
-    <PackageReference Include="PixiEditor.Parser" Version="1.2.0.1" />
+    <PackageReference Include="PixiEditor.Parser" Version="2.0.0" />
+    <PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0" />
+    <PackageReference Include="SkiaSharp" Version="2.80.3" />
+    <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
     <PackageReference Include="WriteableBitmapEx">
       <Version>1.6.7</Version>
     </PackageReference>
@@ -224,6 +248,14 @@
   <ItemGroup>
     <ProjectReference Include="..\PixiEditor.UpdateModule\PixiEditor.UpdateModule.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <Reference Include="PixiParser">
+      <HintPath>..\..\PixiParser\src\PixiParser.Skia\bin\Debug\net5.0\PixiParser.dll</HintPath>
+    </Reference>
+    <Reference Include="PixiParser.Skia">
+      <HintPath>..\..\PixiParser\src\PixiParser.Skia\bin\Debug\net5.0\PixiParser.Skia.dll</HintPath>
+    </Reference>
+  </ItemGroup>
   <ItemGroup>
     <Compile Update="Properties\Settings.Designer.cs">
       <DesignTimeSharedInput>True</DesignTimeSharedInput>

+ 2 - 2
PixiEditor/Properties/AssemblyInfo.cs

@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.6.0")]
-[assembly: AssemblyFileVersion("0.1.6.0")]
+[assembly: AssemblyVersion("0.1.7.0")]
+[assembly: AssemblyFileVersion("0.1.7.0")]

+ 1 - 1
PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -28,7 +28,7 @@ namespace PixiEditor.ViewModels
         {
             get
             {
-#if UPDATE
+#if UPDATE || DEBUG
                 return true;
 #else
                 return false;

+ 2 - 7
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -23,7 +23,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public ClipboardViewModel(ViewModelMain owner)
             : base(owner)
         {
-            CopyCommand = new RelayCommand(Copy, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
+            CopyCommand = new RelayCommand(Copy);
             DuplicateCommand = new RelayCommand(Duplicate, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
             CutCommand = new RelayCommand(Cut, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
             PasteCommand = new RelayCommand(Paste, CanPaste);
@@ -55,12 +55,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void Copy(object parameter)
         {
-            var doc = Owner.BitmapManager.ActiveDocument;
-            ClipboardController.CopyToClipboard(
-                doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray(),
-                doc.ActiveSelection.SelectedPoints.ToArray(),
-                doc.Width,
-                doc.Height);
+            ClipboardController.CopyToClipboard(Owner.BitmapManager.ActiveDocument);
         }
     }
 }

+ 12 - 12
PixiEditor/ViewModels/SubViewModels/Main/ColorsViewModel.cs

@@ -1,6 +1,6 @@
-using System;
-using System.Windows.Media;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
+using SkiaSharp;
+using System;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -12,9 +12,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand RemoveSwatchCommand { get; set; }
 
-        private Color primaryColor = Colors.Black;
+        private SKColor primaryColor = SKColors.Black;
 
-        public Color PrimaryColor // Primary color, hooked with left mouse button
+        public SKColor PrimaryColor // Primary color, hooked with left mouse button
         {
             get => primaryColor;
             set
@@ -28,9 +28,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private Color secondaryColor = Colors.White;
+        private SKColor secondaryColor = SKColors.White;
 
-        public Color SecondaryColor
+        public SKColor SecondaryColor
         {
             get => secondaryColor;
             set
@@ -58,7 +58,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SecondaryColor = tmp;
         }
 
-        public void AddSwatch(Color color)
+        public void AddSwatch(SKColor color)
         {
             if (!Owner.BitmapManager.ActiveDocument.Swatches.Contains(color))
             {
@@ -68,12 +68,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void RemoveSwatch(object parameter)
         {
-            if (!(parameter is Color))
+            if (!(parameter is SKColor))
             {
                 throw new ArgumentException();
             }
 
-            Color color = (Color)parameter;
+            SKColor color = (SKColor)parameter;
             if (Owner.BitmapManager.ActiveDocument.Swatches.Contains(color))
             {
                 Owner.BitmapManager.ActiveDocument.Swatches.Remove(color);
@@ -82,7 +82,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void SelectColor(object parameter)
         {
-            PrimaryColor = parameter as Color? ?? throw new ArgumentException();
+            PrimaryColor = parameter as SKColor? ?? throw new ArgumentException();
         }
     }
-}
+}

+ 2 - 7
PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs

@@ -31,7 +31,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private bool showDocumentName = IPreferences.Current.GetPreference(nameof(ShowDocumentName), true);
+        private bool showDocumentName = IPreferences.Current.GetPreference(nameof(ShowDocumentName), false);
 
         public bool ShowDocumentName
         {
@@ -117,7 +117,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
                 richPresence.WithTimestamps(new Timestamps(document.OpenedUTC));
 
-                richPresence.Details = ShowDocumentName ? $"Editing {document.Name}".Limit(128) : "Editing something (incognito)";
+                richPresence.Details = ShowDocumentName ? $"Editing {document.Name}".Limit(128) : "Editing an image";
 
                 string state = string.Empty;
 
@@ -154,11 +154,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
                 Details = "Staring at absolutely",
                 State = "nothing",
-                Buttons = new Button[]
-                {
-                    new Button() { Label = "Download PixiEditor", Url = "https://www.github.com/PixiEditor/PixiEditor/releases/latest" },
-                    new Button() { Label = "Watch trailer", Url = "https://youtu.be/QKnXBUY0Pqk" }
-                },
 
                 Assets = new Assets
                 {

+ 25 - 0
PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs

@@ -19,6 +19,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand OpenResizePopupCommand { get; set; }
 
+        public RelayCommand RotateToRightCommand { get; set; }
+        public RelayCommand FlipCommand { get; set; }
+
         public DocumentViewModel(ViewModelMain owner)
             : base(owner)
         {
@@ -26,6 +29,28 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             ClipCanvasCommand = new RelayCommand(ClipCanvas, Owner.DocumentIsNotNull);
             DeletePixelsCommand = new RelayCommand(DeletePixels, Owner.SelectionSubViewModel.SelectionIsNotEmpty);
             OpenResizePopupCommand = new RelayCommand(OpenResizePopup, Owner.DocumentIsNotNull);
+            RotateToRightCommand = new RelayCommand(RotateDocument, Owner.DocumentIsNotNull);
+            FlipCommand = new RelayCommand(FlipDocument, Owner.DocumentIsNotNull);
+        }
+
+        public void FlipDocument(object parameter)
+        {
+            if (parameter is "Horizontal")
+            {
+                Owner.BitmapManager.ActiveDocument?.FlipActiveDocument(FlipType.Horizontal);
+            }
+            else if(parameter is "Vertical")
+            {
+                Owner.BitmapManager.ActiveDocument?.FlipActiveDocument(FlipType.Vertical);
+            }
+        }
+
+        public void RotateDocument(object parameter)
+        {
+            if (parameter is double angle)
+            {
+                Owner.BitmapManager.ActiveDocument?.RotateActiveDocument((float)angle);
+            }
         }
 
         public void ClipCanvas(object parameter)

+ 10 - 10
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -1,11 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
+using Microsoft.Win32;
 using Newtonsoft.Json.Linq;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
@@ -15,6 +8,12 @@ using PixiEditor.Models.IO;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Parser;
 using PixiEditor.Views.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -262,7 +261,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandProperty.</param>
         private void ExportFile(object parameter)
         {
-            WriteableBitmap bitmap = Owner.BitmapManager.GetCombinedLayersBitmap();
+            ViewModelMain.Current.ActionDisplay = "";
+            WriteableBitmap bitmap = Owner.BitmapManager.ActiveDocument.Renderer.FinalBitmap;
             Exporter.Export(bitmap, new Size(bitmap.PixelWidth, bitmap.PixelHeight));
         }
 
@@ -310,4 +310,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             return documents;
         }
     }
-}
+}

+ 22 - 14
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -1,9 +1,10 @@
-using System;
-using System.Windows;
-using System.Windows.Input;
-using PixiEditor.Helpers;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers.Shortcuts;
 using PixiEditor.Models.Position;
+using System;
+using System.Windows;
+using System.Windows.Input;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -52,7 +53,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
 
             Owner.ShortcutController.KeyPressed(args.Key, Keyboard.Modifiers);
-            Owner.BitmapManager.SelectedTool.OnKeyDown(args);
+            Owner.ToolsSubViewModel.ActiveTool.OnKeyDown(args);
         }
 
         private void MouseDown(object parameter)
@@ -64,14 +65,16 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
             if (Mouse.LeftButton == MouseButtonState.Pressed)
             {
-                if (!Owner.BitmapManager.MouseController.IsRecordingChanges)
+                BitmapManager bitmapManager = Owner.BitmapManager;
+                var activeDocument = bitmapManager.ActiveDocument;
+                if (!bitmapManager.MouseController.IsRecordingChanges)
                 {
-                    bool clickedOnCanvas = Owner.BitmapManager.ActiveDocument.MouseXOnCanvas >= 0 &&
-                        Owner.BitmapManager.ActiveDocument.MouseXOnCanvas <= Owner.BitmapManager.ActiveDocument.Width &&
-                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas >= 0 &&
-                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas <= Owner.BitmapManager.ActiveDocument.Height;
-                    Owner.BitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
-                    Owner.BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
+                    bool clickedOnCanvas = activeDocument.MouseXOnCanvas >= 0 &&
+                        activeDocument.MouseXOnCanvas <= activeDocument.Width &&
+                        activeDocument.MouseYOnCanvas >= 0 &&
+                        activeDocument.MouseYOnCanvas <= activeDocument.Height;
+                    bitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
+                    bitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
                 }
             }
 
@@ -79,6 +82,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Mouse.PrimaryDevice,
                 (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
 
+            Coordinates cords = new Coordinates(
+                (int)Owner.BitmapManager.ActiveDocument.MouseXOnCanvas,
+                (int)Owner.BitmapManager.ActiveDocument.MouseYOnCanvas);
+            Owner.BitmapManager.MouseController.MouseDownCoordinates(cords);
+
             // Mouse down is guaranteed to only be raised from within this application, so by subscribing here we
             // only listen for mouse up events that occurred as a result of a mouse down within this application.
             // This seems better than maintaining a global listener indefinitely.
@@ -120,7 +128,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 ShortcutController.BlockShortcutExecution = false;
             }
 
-            Owner.BitmapManager.SelectedTool.OnKeyUp(args);
+            Owner.ToolsSubViewModel.ActiveTool.OnKeyUp(args);
         }
     }
-}
+}

+ 24 - 31
PixiEditor/ViewModels/SubViewModels/Main/StylusViewModel.cs

@@ -1,5 +1,7 @@
 using System.Windows;
 using System.Windows.Input;
+using GalaSoft.MvvmLight.CommandWpf;
+using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.UserPreferences;
@@ -37,45 +39,31 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private Tool PreviousTool { get; set; }
 
-        public StylusViewModel()
-            : this(null)
-        {
-        }
+        public RelayCommand<StylusButtonEventArgs> StylusDownCommand { get; }
 
-        public StylusViewModel(ViewModelMain owner)
-            : base(owner)
-        {
-        }
+        public RelayCommand<StylusButtonEventArgs> StylusUpCommand { get; }
 
-        public void SetOwner(ViewModelMain owner)
-        {
-            if (Owner is not null)
-            {
-                throw new System.Exception($"{nameof(StylusViewModel)} already has an owner");
-            }
-            else if (owner is null)
-            {
-                return;
-            }
-
-            Owner = owner;
+        public RelayCommand<StylusEventArgs> StylusOutOfRangeCommand { get; }
 
-            // TODO: Only capture it on the Drawing View Port
-            Window mw = Application.Current.MainWindow;
+        public RelayCommand<StylusSystemGestureEventArgs> StylusGestureCommand { get; }
 
-            mw.PreviewStylusButtonDown += Mw_StylusButtonDown;
-            mw.PreviewStylusButtonUp += Mw_StylusButtonUp;
-            mw.PreviewStylusSystemGesture += Mw_PreviewStylusSystemGesture;
+        public StylusViewModel(ViewModelMain owner)
+            : base(owner)
+        {
+            StylusDownCommand = new(StylusDown);
+            StylusUpCommand = new(StylusUp);
+            StylusOutOfRangeCommand = new(StylusOutOfRange);
+            StylusGestureCommand = new(StylusSystemGesture);
 
             isPenModeEnabled = IPreferences.Current.GetLocalPreference<bool>(nameof(IsPenModeEnabled));
-            Owner.BitmapManager.AddPropertyChangedCallback(nameof(Owner.BitmapManager.SelectedTool), UpdateUseTouchGesture);
+            Owner.ToolsSubViewModel.AddPropertyChangedCallback(nameof(ToolsViewModel.ActiveTool), UpdateUseTouchGesture);
 
             UpdateUseTouchGesture();
         }
 
         private void UpdateUseTouchGesture()
         {
-            if (Owner.BitmapManager.SelectedTool is not (MoveViewportTool or ZoomTool))
+            if (Owner.ToolsSubViewModel.ActiveTool is not (MoveViewportTool or ZoomTool))
             {
                 UseTouchGestures = IsPenModeEnabled;
             }
@@ -85,7 +73,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private void Mw_PreviewStylusSystemGesture(object sender, StylusSystemGestureEventArgs e)
+        private void StylusOutOfRange(StylusEventArgs e)
+        {
+            Owner.BitmapManager.HighlightPixels(new Coordinates(-1, -1));
+        }
+
+        private void StylusSystemGesture(StylusSystemGestureEventArgs e)
         {
             if (e.SystemGesture == SystemGesture.Drag || e.SystemGesture == SystemGesture.Tap)
             {
@@ -95,19 +88,19 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             e.Handled = true;
         }
 
-        private void Mw_StylusButtonDown(object sender, StylusButtonEventArgs e)
+        private void StylusDown(StylusButtonEventArgs e)
         {
             e.Handled = true;
 
             if (e.StylusButton.Guid == StylusPointProperties.TipButton.Id && e.Inverted)
             {
-                PreviousTool = Owner.BitmapManager.SelectedTool;
+                PreviousTool = Owner.ToolsSubViewModel.ActiveTool;
                 Owner.ToolsSubViewModel.SetActiveTool<EraserTool>();
                 ToolSetByStylus = true;
             }
         }
 
-        private void Mw_StylusButtonUp(object sender, StylusButtonEventArgs e)
+        private void StylusUp(StylusButtonEventArgs e)
         {
             e.Handled = true;
 

+ 48 - 25
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -1,18 +1,26 @@
 using System;
+using System.Collections;
+using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Reflection;
 using System.Windows.Input;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class ToolsViewModel : SubViewModel<ViewModelMain>
     {
         private Cursor toolCursor;
+        private Tool activeTool;
 
         public RelayCommand SelectToolCommand { get; set; } // Command that handles tool switching.
 
@@ -20,8 +28,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public Tool LastActionTool { get; private set; }
 
-        public ObservableCollection<Tool> ToolSet { get; set; }
-
         public Cursor ToolCursor
         {
             get => toolCursor;
@@ -32,26 +38,45 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
+        public Tool ActiveTool
+        {
+            get => activeTool;
+            set => SetProperty(ref activeTool, value);
+        }
+
+        public int ToolSize
+        {
+            get => ActiveTool.Toolbar.GetSetting<SizeSetting>("ToolSize") != null
+            ? ActiveTool.Toolbar.GetSetting<SizeSetting>("ToolSize").Value
+            : 1;
+            set
+            {
+                if (ActiveTool.Toolbar.GetSetting<SizeSetting>("ToolSize") is SizeSetting toolSize)
+                {
+                    toolSize.Value = value;
+                    Owner.BitmapManager.HighlightPixels(MousePositionConverter.CurrentCoordinates);
+                }
+            }
+        }
+
+        public IEnumerable<Tool> ToolSet { get; private set; }
+
+        public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
+
         public ToolsViewModel(ViewModelMain owner)
             : base(owner)
         {
             SelectToolCommand = new RelayCommand(SetTool, Owner.DocumentIsNotNull);
             ChangeToolSizeCommand = new RelayCommand(ChangeToolSize);
-
-            Owner.BitmapManager.BitmapOperations.BitmapChanged += (_, _) => TriggerCacheOutdated();
-            Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
         }
 
         public void SetupTools(IServiceProvider services)
         {
-            ToolSet = new ObservableCollection<Tool>(
-                new ToolBuilder(services)
-                .Add<MoveViewportTool>().Add<MoveTool>().Add<PenTool>().Add<SelectTool>().Add<MagicWandTool>().Add<FloodFill>()
-                .Add<LineTool>().Add<CircleTool>().Add<RectangleTool>().Add<EraserTool>().Add<ColorPickerTool>()
-                .Add<BrightnessTool>().Add<ZoomTool>()
-                .Build());
-
-            SetActiveTool<MoveViewportTool>();
+            ToolSet = services.GetServices<Tool>();
+            SetActiveTool<PenTool>();
+
+            Owner.BitmapManager.BitmapOperations.BitmapChanged += (_, _) => TriggerCacheOutdated();
+            Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
         }
 
         public void SetActiveTool<T>()
@@ -62,15 +87,18 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void SetActiveTool(Tool tool)
         {
-            Tool activeTool = ToolSet.FirstOrDefault(x => x.IsActive);
-            if (activeTool != null)
+            if (ActiveTool != null)
             {
                 activeTool.IsActive = false;
+                ActiveTool.OnDeselected();
             }
 
+            LastActionTool = ActiveTool;
+            ActiveTool = tool;
+            SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
+
             tool.IsActive = true;
-            LastActionTool = Owner.BitmapManager.SelectedTool;
-            Owner.BitmapManager.SetActiveTool(tool);
+            ActiveTool.OnSelected();
             SetToolCursor(tool.GetType());
 
             if (Owner.StylusSubViewModel != null)
@@ -135,20 +163,15 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         private void ChangeToolSize(object parameter)
         {
             int increment = (int)parameter;
-            int newSize = Owner.BitmapManager.ToolSize + increment;
+            int newSize = ToolSize + increment;
             if (newSize > 0)
             {
-                Owner.BitmapManager.ToolSize = newSize;
+                ToolSize = newSize;
             }
         }
 
         private void SetActiveTool(Type toolType)
         {
-            if (toolType == null && toolType.IsAssignableTo(typeof(Tool)))
-            {
-                return;
-            }
-
             Tool foundTool = ToolSet.First(x => x.GetType() == toolType);
             SetActiveTool(foundTool);
         }
@@ -157,7 +180,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             if (tool != null)
             {
-                ToolCursor = Owner.BitmapManager.SelectedTool.Cursor;
+                ToolCursor = ActiveTool.Cursor;
             }
             else
             {

+ 5 - 29
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -15,21 +15,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand RedoCommand { get; set; }
 
-        private LayerChange[] undoChanges;
-
-        public LayerChange[] UndoChanges // This acts like UndoManager process, but it was implemented before process system, so it can be transformed into it
-        {
-            get => undoChanges;
-            set
-            {
-                undoChanges = value;
-                for (int i = 0; i < value.Length; i++)
-                {
-                    Owner.BitmapManager.ActiveDocument.Layers.First(x => x.LayerGuid == value[i].LayerGuid).SetPixels(value[i].PixelChanges);
-                }
-            }
-        }
-
         public UndoViewModel(ViewModelMain owner)
             : base(owner)
         {
@@ -43,21 +28,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             ClearUndoTempDirectory();
         }
 
-        public void TriggerNewUndoChange(Tool toolUsed)
+        public void TriggerNewUndoChange(Tool selectedTool)
         {
-            if (BitmapManager.IsOperationTool(toolUsed)
-                && ((BitmapOperationTool)toolUsed).UseDefaultUndoMethod)
-            {
-                Tuple<LayerChange, LayerChange>[] changes = Owner.ChangesController.PopChanges();
-                if (changes != null && changes.Length > 0)
-                {
-                    LayerChange[] newValues = changes.Select(x => x.Item1).ToArray();
-                    LayerChange[] oldValues = changes.Select(x => x.Item2).ToArray();
-                    Owner.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
-                        new Change("UndoChanges", oldValues, newValues, root: this));
-                    toolUsed.AfterAddedUndo(Owner.BitmapManager.ActiveDocument.UndoManager);
-                }
-            }
+            var activeDoc = Owner.BitmapManager.ActiveDocument;
+            if (activeDoc is null) return;
+
+            selectedTool.AddUndoProcess(activeDoc);
         }
 
         /// <summary>

+ 19 - 5
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -2,7 +2,7 @@
 using System.ComponentModel;
 using System.Diagnostics;
 using System.IO;
-using System.Reflection;
+using System.Linq;
 using System.Threading.Tasks;
 using System.Windows;
 using PixiEditor.Helpers;
@@ -19,6 +19,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public UpdateChecker UpdateChecker { get; set; }
 
+        public UpdateChannel[] UpdateChannels { get; } = new UpdateChannel[2];
+
         public RelayCommand RestartApplicationCommand { get; set; }
 
         private string versionText;
@@ -42,7 +44,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 RaisePropertyChanged(nameof(UpdateReadyToInstall));
                 if (value)
                 {
-                    VersionText = $"to install update (current {UpdateChecker.CurrentVersionTag})"; // Button shows "Restart" before this text
+                    VersionText = $"to install update (current {VersionHelpers.GetCurrentAssemblyVersionString()})"; // Button shows "Restart" before this text
                 }
             }
         }
@@ -52,6 +54,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             Owner.OnStartupEvent += Owner_OnStartupEvent;
             RestartApplicationCommand = new RelayCommand(RestartApplication);
+            IPreferences.Current.AddCallback<string>("UpdateChannel", (val) => UpdateChecker.Channel = GetUpdateChannel(val));
             InitUpdateChecker();
         }
 
@@ -134,7 +137,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private static void OpenExeInstaller(string updateExeFile)
         {
-            bool alreadyUpdated = AssemblyHelper.GetCurrentAssemblyVersion().ToString() ==
+            bool alreadyUpdated = VersionHelpers.GetCurrentAssemblyVersion().ToString() ==
                     updateExeFile.Split('-')[1].Split(".exe")[0];
 
             if (!alreadyUpdated)
@@ -191,9 +194,20 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void InitUpdateChecker()
         {
-            string version = AssemblyHelper.GetCurrentAssemblyVersion().ToString();
-            UpdateChecker = new UpdateChecker(version);
+            UpdateChannels[0] = new UpdateChannel("Release", "PixiEditor", "PixiEditor");
+            UpdateChannels[1] = new UpdateChannel("Development", "PixiEditor", "PixiEditor-development-channel");
+
+            string updateChannel = IPreferences.Current.GetPreference<string>("UpdateChannel");
+
+            string version = VersionHelpers.GetCurrentAssemblyVersionString();
+            UpdateChecker = new UpdateChecker(version, GetUpdateChannel(updateChannel));
             VersionText = $"Version {version}";
         }
+
+        private UpdateChannel GetUpdateChannel(string channelName)
+        {
+            UpdateChannel selectedChannel = UpdateChannels.FirstOrDefault(x => x.Name == channelName, UpdateChannels[0]);
+            return selectedChannel;
+        }
     }
 }

+ 2 - 2
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/DiscordSettings.cs

@@ -14,7 +14,7 @@
             }
         }
 
-        private bool showDocumentName = GetPreference(nameof(ShowDocumentName), true);
+        private bool showDocumentName = GetPreference(nameof(ShowDocumentName), false);
 
         public bool ShowDocumentName
         {
@@ -57,7 +57,7 @@
         {
             get
             {
-                return ShowDocumentName ? $"Editing coolPixelArt.pixi" : "Editing something (incognito)";
+                return ShowDocumentName ? $"Editing coolPixelArt.pixi" : "Editing an image";
             }
         }
 

+ 22 - 1
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs

@@ -1,4 +1,8 @@
-namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+using PixiEditor.ViewModels.SubViewModels.Main;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
 {
     public class UpdateSettings : SettingsGroup
     {
@@ -14,5 +18,22 @@
                 RaiseAndUpdatePreference(name, value);
             }
         }
+
+        private string updateChannelName = GetPreference("UpdateChannel", "Release");
+
+        public string UpdateChannelName
+        {
+            get => updateChannelName;
+            set
+            {
+                updateChannelName = value;
+                RaiseAndUpdatePreference("UpdateChannel", value);
+            }
+        }
+
+        public IEnumerable<string> UpdateChannels
+        {
+            get => ViewModelMain.Current.UpdateSubViewModel.UpdateChannels.Select(x => x.Name);
+        }
     }
 }

+ 1 - 4
PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Configuration;
-using PixiEditor.Models.UserPreferences;
-using PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings;
+using PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
 {

+ 23 - 55
PixiEditor/ViewModels/ViewModelMain.cs

@@ -70,8 +70,6 @@ namespace PixiEditor.ViewModels
 
         public BitmapManager BitmapManager { get; set; }
 
-        public PixelChangesController ChangesController { get; set; }
-
         public ShortcutController ShortcutController { get; set; }
 
         public StylusViewModel StylusSubViewModel { get; set; }
@@ -89,7 +87,7 @@ namespace PixiEditor.ViewModels
                     return actionDisplay;
                 }
 
-                return BitmapManager.SelectedTool.ActionDisplay;
+                return ToolsSubViewModel.ActiveTool.ActionDisplay;
             }
             set
             {
@@ -120,17 +118,9 @@ namespace PixiEditor.ViewModels
 #endif
         }
 
-        public ViewModelMain(IServiceCollection services)
+        public ViewModelMain(IServiceProvider serviceProvider)
         {
             Current = this;
-
-            ConfigureServices(services);
-            Setup(services.BuildServiceProvider());
-        }
-
-        public void ConfigureServices(IServiceCollection collection)
-        {
-            collection.AddSingleton(this);
         }
 
         public void Setup(IServiceProvider services)
@@ -140,34 +130,32 @@ namespace PixiEditor.ViewModels
             Preferences = services.GetRequiredService<IPreferences>();
 
             Preferences.Init();
-
             BitmapManager = services.GetRequiredService<BitmapManager>();
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
             BitmapManager.MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
 
-            SelectionSubViewModel = new SelectionViewModel(this);
+            SelectionSubViewModel = services.GetService<SelectionViewModel>();
 
-            ChangesController = new PixelChangesController();
             OnStartupCommand = new RelayCommand(OnStartup);
             CloseWindowCommand = new RelayCommand(CloseWindow);
 
-            FileSubViewModel = new FileViewModel(this);
-            ToolsSubViewModel = GetSubViewModel<ToolsViewModel>(services);
-            ToolsSubViewModel.SetupTools(services);
+            FileSubViewModel = services.GetService<FileViewModel>();
+            ToolsSubViewModel = services.GetService<ToolsViewModel>();
+            ToolsSubViewModel?.SetupTools(services);
 
-            IoSubViewModel = new IoViewModel(this);
-            LayersSubViewModel = new LayersViewModel(this);
-            ClipboardSubViewModel = new ClipboardViewModel(this);
-            UndoSubViewModel = new UndoViewModel(this);
-            ViewportSubViewModel = new ViewportViewModel(this);
-            ColorsSubViewModel = new ColorsViewModel(this);
-            DocumentSubViewModel = new DocumentViewModel(this);
-            DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
-            UpdateSubViewModel = new UpdateViewModel(this);
+            IoSubViewModel = services.GetService<IoViewModel>();
+            LayersSubViewModel = services.GetService<LayersViewModel>();
+            ClipboardSubViewModel = services.GetService<ClipboardViewModel>();
+            UndoSubViewModel = services.GetService<UndoViewModel>();
+            ViewportSubViewModel = services.GetService<ViewportViewModel>();
+            ColorsSubViewModel = services.GetService<ColorsViewModel>();
+            DocumentSubViewModel = services.GetService<DocumentViewModel>();
+            DiscordViewModel = services.GetService<DiscordViewModel>();
+            UpdateSubViewModel = services.GetService<UpdateViewModel>();
 
-            WindowSubViewModel = GetSubViewModel<WindowViewModel>(services, false);
-            StylusSubViewModel = GetSubViewModel<StylusViewModel>(services);
+            WindowSubViewModel = services.GetService<WindowViewModel>();
+            StylusSubViewModel = services.GetService<StylusViewModel>();
 
             AddDebugOnlyViewModels();
             AddReleaseOnlyViewModels();
@@ -181,7 +169,7 @@ namespace PixiEditor.ViewModels
                         CreateToolShortcut<RectangleTool>(Key.R, "Select Rectangle Tool"),
                         CreateToolShortcut<CircleTool>(Key.C, "Select Circle Tool"),
                         CreateToolShortcut<LineTool>(Key.L, "Select Line Tool"),
-                        CreateToolShortcut<FloodFill>(Key.G, "Select Flood Fill Tool"),
+                        CreateToolShortcut<FloodFillTool>(Key.G, "Select Flood Fill Tool"),
                         CreateToolShortcut<BrightnessTool>(Key.U, "Select Brightness Tool"),
                         CreateToolShortcut<MoveTool>(Key.V, "Select Move Tool"),
                         CreateToolShortcut<SelectTool>(Key.M, "Select Select Tool"),
@@ -221,7 +209,7 @@ namespace PixiEditor.ViewModels
                         "View",
                         new Shortcut(Key.OemTilde, ViewportSubViewModel.ToggleGridLinesCommand, "Toggle gridlines", modifier: ModifierKeys.Control)));
 
-            MiscSubViewModel = new MiscViewModel(this);
+            MiscSubViewModel = services.GetService<MiscViewModel>();
 
             // Add F1 shortcut after MiscSubViewModel is constructed
             ShortcutController.ShortcutGroups.Add(
@@ -231,7 +219,7 @@ namespace PixiEditor.ViewModels
 
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
 
-            BitmapManager.SelectedToolChanged += BitmapManager_SelectedToolChanged;
+            ToolsSubViewModel.SelectedToolChanged += BitmapManager_SelectedToolChanged;
         }
 
         /// <summary>
@@ -241,7 +229,7 @@ namespace PixiEditor.ViewModels
         {
             foreach (var document in BitmapManager.Documents)
             {
-                document.PreviewLayer = null;
+                document.PreviewLayer.Reset();
             }
         }
 
@@ -374,36 +362,16 @@ namespace PixiEditor.ViewModels
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
         {
-            UndoSubViewModel.TriggerNewUndoChange(BitmapManager.SelectedTool);
+            UndoSubViewModel.TriggerNewUndoChange(ToolsSubViewModel.ActiveTool);
         }
 
         private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
         {
-            ChangesController.AddChanges(
-                new LayerChange(e.PixelsChanged, e.ChangedLayerGuid),
-                new LayerChange(e.OldPixelsValues, e.ChangedLayerGuid));
             BitmapManager.ActiveDocument.ChangesSaved = false;
-            if (BitmapManager.IsOperationTool())
+            if (ToolsSubViewModel.ActiveTool is BitmapOperationTool)
             {
                 ColorsSubViewModel.AddSwatch(ColorsSubViewModel.PrimaryColor);
             }
         }
-
-        private T GetSubViewModel<T>(IServiceProvider services, bool isRequired = true)
-        {
-            T subViewModel = services.GetService<T>();
-
-            if (subViewModel is null && isRequired)
-            {
-                throw new InvalidOperationException($"No required view model for type '{typeof(T)}' has been registered.");
-            }
-
-            if (subViewModel is ISettableOwner<ViewModelMain> settable)
-            {
-                settable.SetOwner(this);
-            }
-
-            return subViewModel;
-        }
     }
 }

+ 3 - 1
PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -6,8 +6,10 @@
         xmlns:dataHolders="clr-namespace:PixiEditor.Models.DataHolders" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:sys="clr-namespace:System;assembly=System.Runtime"
         xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
+        xmlns:local="clr-namespace:PixiEditor.Views.Dialogs"
         mc:Ignorable="d" ShowInTaskbar="False"
         Title="Hello there!" Height="662" Width="632"
+        d:DataContext="{d:DesignInstance local:HelloTherePopup}"
         WindowStyle="None" WindowStartupLocation="CenterScreen">
 
     <Window.Resources>
@@ -117,7 +119,7 @@
                                                 Style="{StaticResource DarkRoundButton}"
                                                 x:Name="fileButton">
                                             <Grid Width="100" Height="100">
-                                                <Image Source="{Binding PreviewBitmap}" Margin="20"/>
+                                                <Image Source="{Binding PreviewBitmap}" RenderOptions.BitmapScalingMode="NearestNeighbor" Margin="20"/>
                                                 <Border Grid.Row="1" Height="8" Width="8" x:Name="extensionBorder" Margin="5"
                                                         Background="{Binding FileExtension, Converter={converters:FileExtensionToColorConverter}}" 
                                                         VerticalAlignment="Bottom" HorizontalAlignment="Right">

+ 1 - 1
PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -22,7 +22,7 @@ namespace PixiEditor.Views.Dialogs
             DependencyProperty.Register(nameof(RecentlyOpenedEmpty), typeof(bool), typeof(HelloTherePopup));
 
         public static string VersionText =>
-            $"v{AssemblyHelper.GetCurrentAssemblyVersion(x => $"{x.Major}.{x.Minor}" + (x.Build != 0 ? $".{x.Build}" : ""))}";
+            $"v{VersionHelpers.GetCurrentAssemblyVersionString()}";
 
         public FileViewModel FileViewModel { get => (FileViewModel)GetValue(FileViewModelProperty); set => SetValue(FileViewModelProperty, value); }
 

+ 6 - 0
PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -82,6 +82,12 @@
                     <Label Style="{StaticResource Header1}" Content="Auto-updates"/>
                     <StackPanel Orientation="Vertical" Margin="50 0 50 0">
                         <CheckBox IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}" Content="Check updates on startup"/>
+                        <StackPanel Orientation="Horizontal">
+                        <Label VerticalAlignment="Center" Content="Update source" Style="{StaticResource BaseLabel}"/>
+                            <ComboBox Width="110" Margin="0 10 0 10" HorizontalAlignment="Left" 
+                                      ItemsSource="{Binding SettingsSubViewModel.Update.UpdateChannels}"
+                                      SelectedValue="{Binding SettingsSubViewModel.Update.UpdateChannelName}"/>
+                        </StackPanel>
                     </StackPanel>
                 </StackPanel>
             </Grid>

+ 52 - 25
PixiEditor/Views/MainWindow.xaml

@@ -13,7 +13,7 @@
         xmlns:cmd="http://www.galasoft.ch/mvvmlight" 
         xmlns:avalondock="https://github.com/Dirkster99/AvalonDock"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker" xmlns:usercontrols="clr-namespace:PixiEditor.Views.UserControls" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
-        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock" xmlns:layerUserControls="clr-namespace:PixiEditor.Views.UserControls.Layers" d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
+        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock" xmlns:layerUserControls="clr-namespace:PixiEditor.Views.UserControls.Layers" xmlns:sys="clr-namespace:System;assembly=System.Runtime" d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
         mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
         WindowStartupLocation="CenterScreen" WindowState="Maximized">
@@ -24,6 +24,14 @@
     <Window.Resources>
         <ResourceDictionary>
             <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
+            <converters:BoolToIntConverter x:Key="BoolToIntConverter" />
+            <converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter" />
+            <converters:LayersToStructuredLayersConverter x:Key="LayersToStructuredLayersConverter"/>
+            <converters:DoubleToIntConverter x:Key="DoubleToIntConverter"/>
+            <converters:IsSpecifiedTypeConverter SpecifiedType="{x:Type tools:ZoomTool}" x:Key="IsZoomToolConverter"/>
+            <converters:IsSpecifiedTypeConverter SpecifiedType="{x:Type tools:MoveViewportTool}" x:Key="IsMoveViewportToolConverter"/>
+            <converters:SKColorToMediaColorConverter x:Key="SKColorToMediaColorConverter"/>
+            <converters:DockingManagerActiveContentConverter x:Key="DockingManagerActiveContentConverter"/>
             <ResourceDictionary.MergedDictionaries>
                 <ResourceDictionary Source="pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml" />
             </ResourceDictionary.MergedDictionaries>
@@ -126,7 +134,22 @@
                     <MenuItem Header="_Clip Canvas" Command="{Binding DocumentSubViewModel.ClipCanvasCommand}" />
                     <Separator/>
                     <MenuItem Header="_Center Content" Command="{Binding DocumentSubViewModel.CenterContentCommand}" />
-                </MenuItem>
+                    <!--<Separator/>
+                    <MenuItem Header="_Rotate to right 90&#186;" Command="{Binding DocumentSubViewModel.RotateToRightCommand}">
+                        <MenuItem.CommandParameter>
+                            <sys:Double>90</sys:Double>
+                        </MenuItem.CommandParameter>
+                    </MenuItem>
+                    <MenuItem Header="_Rotate to left 90&#186;" Command="{Binding DocumentSubViewModel.RotateToRightCommand}">
+                        <MenuItem.CommandParameter>
+                            <sys:Double>-90</sys:Double>
+                        </MenuItem.CommandParameter>
+                    </MenuItem>
+                    <Separator/>
+                    <MenuItem Header="_Flip Horizontal" Command="{Binding DocumentSubViewModel.FlipCommand}" CommandParameter="Horizontal"/>
+                    <MenuItem Header="_Flip Vertical" Command="{Binding DocumentSubViewModel.FlipCommand}" CommandParameter="Vertical"/>
+                -->
+                    </MenuItem> 
                 <MenuItem Header="_View">
                     <MenuItem Header="_Show Grid Lines" IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
                               IsCheckable="True" InputGestureText="Ctrl+`"/>
@@ -213,9 +236,9 @@
             </ToggleButton>
             <Grid Margin="5,5,10,5" Background="{StaticResource BrighterAccentColor}" Width="5"/>
             <Label Style="{StaticResource BaseLabel}" FontSize="12"
-                   VerticalAlignment="Center" Content="{Binding BitmapManager.SelectedTool.DisplayName}"
-                   ToolTip="{Binding BitmapManager.SelectedTool.ActionDisplay}"/>
-            <ItemsControl ItemsSource="{Binding BitmapManager.SelectedTool.Toolbar.Settings}">
+                   VerticalAlignment="Center" Content="{Binding ToolsSubViewModel.ActiveTool.DisplayName}"
+                   ToolTip="{Binding ToolsSubViewModel.ActiveTool.ActionDisplay}"/>
+            <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ActiveTool.Toolbar.Settings}">
                 <ItemsControl.ItemsPanel>
                     <ItemsPanelTemplate>
                         <StackPanel Orientation="Horizontal" Margin="10, 0, 0, 0" />
@@ -235,7 +258,7 @@
         </StackPanel>
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030">
             <Grid AllowDrop="True" Drop="MainWindow_Drop">
-                <DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay}" 
+                <DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay, Converter={StaticResource DockingManagerActiveContentConverter}}" 
                                            DocumentsSource="{Binding BitmapManager.Documents}">
                     <DockingManager.Theme>
                         <avalonDockTheme:PixiEditorDockTheme />
@@ -264,9 +287,13 @@
                                         MouseDownCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseDownCommand}"
                                         MouseXOnCanvas="{Binding MouseXOnCanvas, Mode=TwoWay}"
                                         MouseYOnCanvas="{Binding MouseYOnCanvas, Mode=TwoWay}"
+                                        StylusButtonDownCommand="{Binding XamlAccesibleViewModel.StylusSubViewModel.StylusDownCommand}"
+                                        StylusButtonUpCommand="{Binding XamlAccesibleViewModel.StylusSubViewModel.StylusUpCommand}"
+                                        StylusGestureCommand="{Binding XamlAccesibleViewModel.StylusSubViewModel.StylusGestureCommand}"
+                                        StylusOutOfRangeCommand="{Binding XamlAccesibleViewModel.StylusSubViewModel.StylusOutOfRangeCommand}"
                                         UseTouchGestures="{Binding XamlAccesibleViewModel.StylusSubViewModel.UseTouchGestures}"
-                                        IsUsingZoomTool="{Binding XamlAccesibleViewModel.BitmapManager.SelectedTool, Converter={converters:IsSpecifiedTypeConverter SpecifiedType={x:Type tools:ZoomTool}}}"
-                                        IsUsingMoveViewportTool="{Binding XamlAccesibleViewModel.BitmapManager.SelectedTool, Converter={converters:IsSpecifiedTypeConverter SpecifiedType={x:Type tools:MoveViewportTool}}}"
+                                        IsUsingZoomTool="{Binding XamlAccesibleViewModel.ToolsSubViewModel.ActiveTool, Converter={converters:IsSpecifiedTypeConverter SpecifiedType={x:Type tools:ZoomTool}}}"
+                                        IsUsingMoveViewportTool="{Binding XamlAccesibleViewModel.ToolsSubViewModel.ActiveTool, Converter={converters:IsSpecifiedTypeConverter SpecifiedType={x:Type tools:MoveViewportTool}}}"
                                         Stylus.IsTapFeedbackEnabled="False" Stylus.IsTouchFeedbackEnabled="False">
                                         <i:Interaction.Triggers>
                                             <i:EventTrigger EventName="PreviewMouseDown">
@@ -297,8 +324,8 @@
                                     <LayoutAnchorable ContentId="colorPicker" Title="Color Picker" CanHide="False"
                                                              CanClose="False" CanAutoHide="False" x:Name="colorPickerPanel"
                                                              CanDockAsTabbedDocument="False" CanFloat="True">
-                                        <usercontrols:SmallColorPicker SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"
-                                                                         SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay}" 
+                                        <usercontrols:SmallColorPicker SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay, Converter={StaticResource SKColorToMediaColorConverter}}"
+                                                                         SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay, Converter={StaticResource SKColorToMediaColorConverter}}" 
                                                                          Style="{StaticResource DefaultColorPickerStyle}" x:Name="mainColorPicker">
                                             <i:Interaction.Behaviors>
                                                 <behaviours:GlobalShortcutFocusBehavior/>
@@ -350,7 +377,7 @@
                                                       CanDockAsTabbedDocument="False" CanFloat="True">
                                         <usercontrols:PreviewWindow 
                                             Document="{Binding BitmapManager.ActiveDocument}"
-                                            PrimaryColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"/>
+                                            PrimaryColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay, Converter={StaticResource SKColorToMediaColorConverter}}"/>
                                     </LayoutAnchorable>
                                 </LayoutAnchorablePane>
                             </LayoutAnchorablePaneGroup>
@@ -362,30 +389,30 @@
 
         <Border Grid.Row="2" Grid.Column="0"
                     Background="{StaticResource AccentColor}" Grid.RowSpan="2" CornerRadius="5,0,5,5">
-        <StackPanel Orientation="Vertical" Cursor="Arrow" >
+            <StackPanel Orientation="Vertical" Cursor="Arrow" >
 
-            <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ToolSet}">
-                <ItemsControl.ItemTemplate>
-                    <DataTemplate>
-                        <Button BorderBrush="White"
-                                BorderThickness="{Binding IsActive, Converter={converters:BoolToIntConverter}}"
+                <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ToolSet}">
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate>
+                            <Button BorderBrush="White"                                
+                                BorderThickness="{Binding IsActive, Converter={StaticResource BoolToIntConverter}}"
                                 Style="{StaticResource ToolButtonStyle}"
                                 Command="{Binding Path=DataContext.ToolsSubViewModel.SelectToolCommand,
                                                   RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                 CommandParameter="{Binding}" ToolTip="{Binding Tooltip}">
-                            <Button.Background>
-                                <ImageBrush ImageSource="{Binding ImagePath}" Stretch="Uniform" />
-                            </Button.Background>
+                                <Button.Background>
+                                    <ImageBrush ImageSource="{Binding ImagePath}" Stretch="Uniform" />
+                                </Button.Background>
                                 <Button.Resources>
                                     <Style TargetType="Border">
                                         <Setter Property="CornerRadius" Value="2.5"/>
                                     </Style>
                                 </Button.Resources>
-                        </Button>
-                    </DataTemplate>
-                </ItemsControl.ItemTemplate>
-            </ItemsControl>
-        </StackPanel>
+                            </Button>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+            </StackPanel>
         </Border>
 
         <Grid Grid.Row="3" Grid.Column="1">

Some files were not shown because too many files changed in this diff