Browse Source

Merge pull request #15 from flabbet/dev

0.1 Beta - without all tests
Krzysztof Krysiński 5 years ago
parent
commit
b35d78e3f0
100 changed files with 3083 additions and 1537 deletions
  1. 10 3
      PixiEditor/App.xaml
  2. 17 0
      PixiEditor/Exceptions/LengthMismatchException.cs
  3. 20 39
      PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs
  4. 14 26
      PixiEditor/Helpers/Behaviours/HintTextBehavior.cs
  5. 20 27
      PixiEditor/Helpers/Behaviours/MouseBehaviour.cs
  6. 51 67
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  7. 4 12
      PixiEditor/Helpers/Converters/BoolToColorConverter.cs
  8. 4 14
      PixiEditor/Helpers/Converters/BoolToIntConverter.cs
  9. 21 0
      PixiEditor/Helpers/Converters/BoolToInvertedBoolConverter.cs
  10. 24 0
      PixiEditor/Helpers/Converters/ColorToBrushConverter.cs
  11. 17 0
      PixiEditor/Helpers/Converters/FloatNormalizeConverter.cs
  12. 6 15
      PixiEditor/Helpers/Converters/OppositeVisibilityConverter.cs
  13. 2 5
      PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs
  14. 6 11
      PixiEditor/Helpers/Extensions/DictionaryHelper.cs
  15. 3 10
      PixiEditor/Helpers/NotifyableObject.cs
  16. 6 9
      PixiEditor/Helpers/RelayCommand.cs
  17. 3 4
      PixiEditor/Helpers/UI/ReversedOrderStackPanel.cs
  18. 76 0
      PixiEditor/Helpers/UI/RgbColorSlider.cs
  19. 13 0
      PixiEditor/Helpers/Validators/SizeValidationRule.cs
  20. BIN
      PixiEditor/Images/AnchorDot.png
  21. BIN
      PixiEditor/Images/ColorCircle.png
  22. BIN
      PixiEditor/Images/ColorPalette.png
  23. BIN
      PixiEditor/Images/Cross.png
  24. BIN
      PixiEditor/Images/MoveImage.png
  25. BIN
      PixiEditor/Images/PixiEditorLogo.png
  26. BIN
      PixiEditor/Images/SelectImage.png
  27. 31 34
      PixiEditor/Models/Colors/ExColor.cs
  28. 249 0
      PixiEditor/Models/Controllers/BitmapManager.cs
  29. 84 219
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  30. 83 0
      PixiEditor/Models/Controllers/ClipboardController.cs
  31. 13 12
      PixiEditor/Models/Controllers/MouseMovementController.cs
  32. 54 36
      PixiEditor/Models/Controllers/PixelChangesController.cs
  33. 20 0
      PixiEditor/Models/Controllers/ReadonlyToolUtility.cs
  34. 5 7
      PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs
  35. 6 10
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs
  36. 43 51
      PixiEditor/Models/Controllers/UndoManager.cs
  37. 68 0
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  38. 27 9
      PixiEditor/Models/DataHolders/Change.cs
  39. 303 0
      PixiEditor/Models/DataHolders/Document.cs
  40. 24 0
      PixiEditor/Models/DataHolders/LayerChange.cs
  41. 0 19
      PixiEditor/Models/DataHolders/LayerChanges.cs
  42. 80 0
      PixiEditor/Models/DataHolders/NotifyableColor.cs
  43. 64 0
      PixiEditor/Models/DataHolders/Selection.cs
  44. 58 0
      PixiEditor/Models/DataHolders/SerializableDocument.cs
  45. 8 19
      PixiEditor/Models/DataHolders/StackEx.cs
  46. 0 60
      PixiEditor/Models/DataHolders/Tuple.cs
  47. 18 0
      PixiEditor/Models/Dialogs/ConfirmationDialog.cs
  48. 1 8
      PixiEditor/Models/Dialogs/CustomDialog.cs
  49. 41 27
      PixiEditor/Models/Dialogs/ExportFileDialog.cs
  50. 38 23
      PixiEditor/Models/Dialogs/ImportFileDialog.cs
  51. 26 20
      PixiEditor/Models/Dialogs/NewFileDialog.cs
  52. 89 0
      PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs
  53. 15 0
      PixiEditor/Models/Enums/AnchorPoint.cs
  54. 11 0
      PixiEditor/Models/Enums/BrightnessMode.cs
  55. 11 0
      PixiEditor/Models/Enums/CapType.cs
  56. 9 0
      PixiEditor/Models/Enums/ConfirmationType.cs
  57. 3 9
      PixiEditor/Models/Enums/FileType.cs
  58. 6 7
      PixiEditor/Models/Enums/LayerAction.cs
  59. 9 0
      PixiEditor/Models/Enums/SelectionType.cs
  60. 14 0
      PixiEditor/Models/Events/DocumentChangedEventArgs.cs
  61. 26 0
      PixiEditor/Models/IO/BinarySerialization.cs
  62. 36 39
      PixiEditor/Models/IO/Exporter.cs
  63. 10 24
      PixiEditor/Models/IO/FilesManager.cs
  64. 19 12
      PixiEditor/Models/IO/Importer.cs
  65. 23 43
      PixiEditor/Models/ImageManipulation/Morphology.cs
  66. 28 0
      PixiEditor/Models/ImageManipulation/Transform.cs
  67. 0 22
      PixiEditor/Models/Images/BitmapConverter.cs
  68. 84 0
      PixiEditor/Models/Images/BitmapUtils.cs
  69. 0 24
      PixiEditor/Models/Images/ImageGenerator.cs
  70. 17 15
      PixiEditor/Models/Layers/BasicLayer.cs
  71. 379 77
      PixiEditor/Models/Layers/Layer.cs
  72. 4 24
      PixiEditor/Models/Layers/LayerGenerator.cs
  73. 0 54
      PixiEditor/Models/Layers/LightLayer.cs
  74. 29 0
      PixiEditor/Models/Layers/SerializableLayer.cs
  75. 9 13
      PixiEditor/Models/Position/Coordinates.cs
  76. 172 58
      PixiEditor/Models/Position/CoordinatesCalculator.cs
  77. 2 8
      PixiEditor/Models/Position/DoubleCords.cs
  78. 11 11
      PixiEditor/Models/Position/MousePositionConverter.cs
  79. 19 0
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  80. 0 29
      PixiEditor/Models/Tools/BitmapPixelChanges.cs
  81. 9 0
      PixiEditor/Models/Tools/ReadonlyTool.cs
  82. 41 47
      PixiEditor/Models/Tools/ShapeTool.cs
  83. 27 21
      PixiEditor/Models/Tools/Tool.cs
  84. 8 6
      PixiEditor/Models/Tools/ToolSettings/Settings/BoolSetting.cs
  85. 6 11
      PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs
  86. 49 0
      PixiEditor/Models/Tools/ToolSettings/Settings/DropdownSetting.cs
  87. 6 14
      PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs
  88. 10 9
      PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs
  89. 6 9
      PixiEditor/Models/Tools/ToolSettings/Settings/SizeSetting.cs
  90. 3 6
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicShapeToolbar.cs
  91. 2 7
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicToolbar.cs
  92. 5 5
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BrightnessToolToolbar.cs
  93. 2 7
      PixiEditor/Models/Tools/ToolSettings/Toolbars/EmptyToolbar.cs
  94. 12 0
      PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs
  95. 10 18
      PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs
  96. 13 9
      PixiEditor/Models/Tools/ToolType.cs
  97. 61 30
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  98. 77 54
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  99. 25 10
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  100. 15 9
      PixiEditor/Models/Tools/Tools/EarserTool.cs

+ 10 - 3
PixiEditor/App.xaml

@@ -5,9 +5,16 @@
     <Application.Resources>
     <Application.Resources>
         <ResourceDictionary>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
             <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="Styles/MenuButtonStyle.xaml"/>
-                <ResourceDictionary Source="Styles/ThemeStyle.xaml"/>
+                <ResourceDictionary Source="Styles/ThemeColors.xaml" />
+                <ResourceDictionary Source="Styles/MenuButtonStyle.xaml" />
+                <ResourceDictionary Source="Styles/ThemeStyle.xaml" />
+                <ResourceDictionary Source="Styles/Titlebar.xaml" />
+                <ResourceDictionary Source="Styles/ComboBoxDarkStyle.xaml" />
+                <ResourceDictionary Source="Styles/ColorSliderStyle.xaml" />
+                <ResourceDictionary Source="Styles/ColorToggleButton.xaml" />
+                <ResourceDictionary Source="Styles/AnchorPointToggleButtonStyle.xaml" />
+                <ResourceDictionary Source="Styles/DockingManagerStyle.xaml" />
             </ResourceDictionary.MergedDictionaries>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
         </ResourceDictionary>
     </Application.Resources>
     </Application.Resources>
-</Application>
+</Application>

+ 17 - 0
PixiEditor/Exceptions/LengthMismatchException.cs

@@ -0,0 +1,17 @@
+using System;
+
+namespace PixiEditor.Exceptions
+{
+    public class ArrayLengthMismatchException : Exception
+    {
+        public const string DefaultMessage = "First array length doesn't match second array length";
+
+        public ArrayLengthMismatchException() : base(DefaultMessage)
+        {
+        }
+
+        public ArrayLengthMismatchException(string message) : base(message)
+        {
+        }
+    }
+}

+ 20 - 39
PixiEditor/Helpers/Behaviours/AllowableCharactersTextBoxBehavior.cs

@@ -1,9 +1,5 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
 using System.Windows;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
@@ -14,33 +10,23 @@ namespace PixiEditor.Helpers.Behaviours
     public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
     public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
     {
     {
         public static readonly DependencyProperty RegularExpressionProperty =
         public static readonly DependencyProperty RegularExpressionProperty =
-             DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
-             new FrameworkPropertyMetadata(".*"));
+            DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
+                new FrameworkPropertyMetadata(".*"));
+
+        public static readonly DependencyProperty MaxLengthProperty =
+            DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
+                new FrameworkPropertyMetadata(int.MinValue));
+
         public string RegularExpression
         public string RegularExpression
         {
         {
-            get
-            {
-                return (string)base.GetValue(RegularExpressionProperty);
-            }
-            set
-            {
-                base.SetValue(RegularExpressionProperty, value);
-            }
+            get => (string) GetValue(RegularExpressionProperty);
+            set => SetValue(RegularExpressionProperty, value);
         }
         }
 
 
-        public static readonly DependencyProperty MaxLengthProperty =
-            DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
-            new FrameworkPropertyMetadata(int.MinValue));
         public int MaxLength
         public int MaxLength
         {
         {
-            get
-            {
-                return (int)base.GetValue(MaxLengthProperty);
-            }
-            set
-            {
-                base.SetValue(MaxLengthProperty, value);
-            }
+            get => (int) GetValue(MaxLengthProperty);
+            set => SetValue(MaxLengthProperty, value);
         }
         }
 
 
         protected override void OnAttached()
         protected override void OnAttached()
@@ -56,10 +42,7 @@ namespace PixiEditor.Helpers.Behaviours
             {
             {
                 string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
                 string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));
 
 
-                if (!IsValid(text, true))
-                {
-                    e.CancelCommand();
-                }
+                if (!IsValid(text, true)) e.CancelCommand();
             }
             }
             else
             else
             {
             {
@@ -67,7 +50,7 @@ namespace PixiEditor.Helpers.Behaviours
             }
             }
         }
         }
 
 
-        void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
+        private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
         {
         {
             e.Handled = !IsValid(e.Text, false);
             e.Handled = !IsValid(e.Text, false);
         }
         }
@@ -93,21 +76,19 @@ namespace PixiEditor.Helpers.Behaviours
 
 
         private int LengthOfModifiedText(string newText, bool paste)
         private int LengthOfModifiedText(string newText, bool paste)
         {
         {
-            var countOfSelectedChars = this.AssociatedObject.SelectedText.Length;
-            var caretIndex = this.AssociatedObject.CaretIndex;
-            string text = this.AssociatedObject.Text;
+            var countOfSelectedChars = AssociatedObject.SelectedText.Length;
+            var caretIndex = AssociatedObject.CaretIndex;
+            string text = AssociatedObject.Text;
 
 
             if (countOfSelectedChars > 0 || paste)
             if (countOfSelectedChars > 0 || paste)
             {
             {
                 text = text.Remove(caretIndex, countOfSelectedChars);
                 text = text.Remove(caretIndex, countOfSelectedChars);
                 return text.Length + newText.Length;
                 return text.Length + newText.Length;
             }
             }
-            else
-            {
-                var insert = Keyboard.IsKeyToggled(Key.Insert);
 
 
-                return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
-            }
+            var insert = Keyboard.IsKeyToggled(Key.Insert);
+
+            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
         }
         }
     }
     }
-}
+}

+ 14 - 26
PixiEditor/Helpers/Behaviours/HintTextBehavior.cs

@@ -1,31 +1,25 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Interactivity;
 using System.Windows.Interactivity;
 using System.Windows.Media;
 using System.Windows.Media;
 
 
 namespace PixiEditor.Helpers.Behaviours
 namespace PixiEditor.Helpers.Behaviours
 {
 {
-    class HintTextBehavior : Behavior<TextBox>
+    internal class HintTextBehavior : Behavior<TextBox>
     {
     {
+        // Using a DependencyProperty as the backing store for Hint.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty HintProperty =
+            DependencyProperty.Register("Hint", typeof(string), typeof(HintTextBehavior),
+                new PropertyMetadata(string.Empty));
 
 
         private Brush _textColor;
         private Brush _textColor;
 
 
         public string Hint
         public string Hint
         {
         {
-            get { return (string)GetValue(HintProperty); }
-            set { SetValue(HintProperty, value); }
+            get => (string) GetValue(HintProperty);
+            set => SetValue(HintProperty, value);
         }
         }
 
 
-        // Using a DependencyProperty as the backing store for Hint.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty HintProperty =
-            DependencyProperty.Register("Hint", typeof(string), typeof(HintTextBehavior), new PropertyMetadata(string.Empty));
-
-
 
 
         protected override void OnAttached()
         protected override void OnAttached()
         {
         {
@@ -38,25 +32,19 @@ namespace PixiEditor.Helpers.Behaviours
 
 
         private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
         private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
         {
         {
-            if(string.IsNullOrEmpty(AssociatedObject.Text) == true)
-            {
-                SetHint(true);
-            }
+            if (string.IsNullOrEmpty(AssociatedObject.Text)) SetHint(true);
         }
         }
 
 
         private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
         private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
         {
         {
-            if(AssociatedObject.Text == Hint)
-            {
-                SetHint(false);
-            }
+            if (AssociatedObject.Text == Hint) SetHint(false);
         }
         }
 
 
         private void SetHint(bool active)
         private void SetHint(bool active)
         {
         {
-            if (active == true)
+            if (active)
             {
             {
-                AssociatedObject.Foreground = (SolidColorBrush)new BrushConverter().ConvertFromString("#7B7B7B");
+                AssociatedObject.Foreground = (SolidColorBrush) new BrushConverter().ConvertFromString("#7B7B7B");
                 AssociatedObject.Text = Hint;
                 AssociatedObject.Text = Hint;
             }
             }
             else
             else
@@ -69,8 +57,8 @@ namespace PixiEditor.Helpers.Behaviours
         protected override void OnDetaching()
         protected override void OnDetaching()
         {
         {
             base.OnDetaching();
             base.OnDetaching();
-            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;          
+            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
             AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
             AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
         }
         }
     }
     }
-}
+}

+ 20 - 27
PixiEditor/Helpers/Behaviours/MouseBehaviour.cs

@@ -1,45 +1,41 @@
-using PixiEditor.Views;
-using System.Diagnostics;
-using System.Windows;
-using System.Windows.Controls;
+using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
-using Xceed.Wpf.Toolkit.Zoombox;
+using System.Windows.Interactivity;
 
 
-namespace PixiEditor.Helpers.Behaviours {
-
-    public class MouseBehaviour : System.Windows.Interactivity.Behavior<FrameworkElement>
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class MouseBehaviour : Behavior<FrameworkElement>
     {
     {
         public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
         public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
             "MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
             "MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
 
 
+        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
+            "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
+
+        // Using a DependencyProperty as the backing store for RelativeTo.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty RelativeToProperty =
+            DependencyProperty.Register("RelativeTo", typeof(FrameworkElement), typeof(MouseBehaviour),
+                new PropertyMetadata(default(FrameworkElement)));
+
         public double MouseY
         public double MouseY
         {
         {
-            get { return (double)GetValue(MouseYProperty); }
-            set { SetValue(MouseYProperty, value); }
+            get => (double) GetValue(MouseYProperty);
+            set => SetValue(MouseYProperty, value);
         }
         }
 
 
-        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
-            "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));
-
         public double MouseX
         public double MouseX
         {
         {
-            get { return (double)GetValue(MouseXProperty); }
-            set { SetValue(MouseXProperty, value); }
+            get => (double) GetValue(MouseXProperty);
+            set => SetValue(MouseXProperty, value);
         }
         }
 
 
 
 
-
         public FrameworkElement RelativeTo
         public FrameworkElement RelativeTo
         {
         {
-            get { return (FrameworkElement)GetValue(RelativeToProperty); }
-            set { SetValue(RelativeToProperty, value); }
+            get => (FrameworkElement) GetValue(RelativeToProperty);
+            set => SetValue(RelativeToProperty, value);
         }
         }
 
 
-        // Using a DependencyProperty as the backing store for RelativeTo.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty RelativeToProperty =
-            DependencyProperty.Register("RelativeTo", typeof(FrameworkElement), typeof(MouseBehaviour), new PropertyMetadata(default(FrameworkElement)));
-
-
 
 
         protected override void OnAttached()
         protected override void OnAttached()
         {
         {
@@ -48,10 +44,7 @@ namespace PixiEditor.Helpers.Behaviours {
 
 
         private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
         private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
         {
         {
-            if(RelativeTo == null)
-            {
-                RelativeTo = AssociatedObject;
-            }
+            if (RelativeTo == null) RelativeTo = AssociatedObject;
             var pos = mouseEventArgs.GetPosition(RelativeTo);
             var pos = mouseEventArgs.GetPosition(RelativeTo);
             MouseX = pos.X;
             MouseX = pos.X;
             MouseY = pos.Y;
             MouseY = pos.Y;

+ 51 - 67
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
+using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
@@ -11,67 +6,54 @@ using System.Windows.Interactivity;
 
 
 namespace PixiEditor.Helpers.Behaviours
 namespace PixiEditor.Helpers.Behaviours
 {
 {
-    class TextBoxFocusBehavior : Behavior<TextBox>
+    internal class TextBoxFocusBehavior : Behavior<TextBox>
     {
     {
-
-        public bool FillSize
-        {
-            get { return (bool)GetValue(FillSizeProperty); }
-            set { SetValue(FillSizeProperty, value); }
-        }
-
         // Using a DependencyProperty as the backing store for FillSize.  This enables animation, styling, binding, etc...
         // Using a DependencyProperty as the backing store for FillSize.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty FillSizeProperty =
         public static readonly DependencyProperty FillSizeProperty =
-            DependencyProperty.Register("FillSize", typeof(bool), typeof(TextBoxFocusBehavior), new PropertyMetadata(false));
-
-
+            DependencyProperty.Register("FillSize", typeof(bool), typeof(TextBoxFocusBehavior),
+                new PropertyMetadata(false));
 
 
 
 
+        private string _oldText; //Value of textbox before editing
+        private bool _valueConverted; //This bool is used to avoid double convertion if enter is hitted
 
 
-        public FocusNavigationDirection NextFocusDirection
+        public bool FillSize
         {
         {
-            get { return (FocusNavigationDirection)GetValue(NextFocusDirectionProperty); }
-            set { SetValue(NextFocusDirectionProperty, value); }
+            get => (bool) GetValue(FillSizeProperty);
+            set => SetValue(FillSizeProperty, value);
         }
         }
 
 
-        // Using a DependencyProperty as the backing store for NextFocusDirection.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty NextFocusDirectionProperty =
-            DependencyProperty.Register("NextFocusDirection", typeof(FocusNavigationDirection), typeof(TextBoxFocusBehavior), 
-                new PropertyMetadata(FocusNavigationDirection.Up));
-
-
-
-
-
-
+        //Converts number to proper format if enter is clicked and moves focus to next object
+        private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
+        {
+            if (e.Key != Key.Enter) return;
 
 
-        private string _oldText; //Value of textbox before editing
-        private bool _valueConverted = false; //This bool is used to avoid double convertion if enter is hitted
+            ConvertValue();
+            AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
+        }
 
 
         protected override void OnAttached()
         protected override void OnAttached()
         {
         {
             base.OnAttached();
             base.OnAttached();
+            AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
+            AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
+            AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
             AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
             AssociatedObject.LostKeyboardFocus += AssociatedObject_LostKeyboardFocus;
-            AssociatedObject.GotKeyboardFocus += AssociatedObject_GotKeyboardFocus;
             AssociatedObject.KeyUp += AssociatedObject_KeyUp;
             AssociatedObject.KeyUp += AssociatedObject_KeyUp;
-            AssociatedObject.GotMouseCapture += AssociatedObject_GotMouseCapture;
         }
         }
 
 
-        private void AssociatedObject_GotMouseCapture(object sender, System.Windows.Input.MouseEventArgs e)
-        {
-            AssociatedObject.SelectAll(); //Selects all text on mouse click
-        }
-        
-        //Converts number to proper format if enter is clicked and moves focus to next object
-        private void AssociatedObject_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
+        protected override void OnDetaching()
         {
         {
-            if (e.Key != System.Windows.Input.Key.Enter) return;
-
-            ConvertValue();
-            AssociatedObject.MoveFocus(new TraversalRequest(NextFocusDirection));
+            base.OnDetaching();
+            AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
+            AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
+            AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
+            AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
+            AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
         }
         }
 
 
-        private void AssociatedObject_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
+        private void AssociatedObjectGotKeyboardFocus(object sender,
+            KeyboardFocusChangedEventArgs e)
         {
         {
             AssociatedObject.SelectAll();
             AssociatedObject.SelectAll();
             if (FillSize)
             if (FillSize)
@@ -81,36 +63,38 @@ namespace PixiEditor.Helpers.Behaviours
             }
             }
         }
         }
 
 
-        private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
+        private void AssociatedObjectGotMouseCapture(object sender,
+            MouseEventArgs e)
         {
         {
-            ConvertValue();            
+            AssociatedObject.SelectAll();
         }
         }
 
 
-        /// <summary>
-        /// Converts number from textbox to format "number px" ex. "15 px"
-        /// </summary>
-        private void ConvertValue()
+        private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         {
         {
-            if (_valueConverted == true || FillSize == false) return;
-            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", ""), out _) == true)
+            if (!AssociatedObject.IsKeyboardFocusWithin)
             {
             {
-                AssociatedObject.Text = string.Format("{0} {1}", AssociatedObject.Text, "px");
+                AssociatedObject.Focus();
+                e.Handled = true;
             }
             }
-            else //If text in textbox isn't number, set it to old value
-            {
-                AssociatedObject.Text = _oldText;
-            }
-            _valueConverted = true;
         }
         }
 
 
-        protected override void OnDetaching()
+        private void AssociatedObject_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
         {
         {
-            base.OnDetaching();
-            AssociatedObject.LostKeyboardFocus -= AssociatedObject_LostKeyboardFocus;
-            AssociatedObject.GotKeyboardFocus -= AssociatedObject_GotKeyboardFocus;
-            AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
-            AssociatedObject.GotMouseCapture -= AssociatedObject_GotMouseCapture;
+            ConvertValue();
+        }
 
 
+        /// <summary>
+        ///     Converts number from textbox to format "number px" ex. "15 px"
+        /// </summary>
+        private void ConvertValue()
+        {
+            if (_valueConverted || FillSize == false) return;
+
+            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", ""), out int result) && result > 0)
+                AssociatedObject.Text = $"{AssociatedObject.Text} px";
+            else //If text in textbox isn't number, set it to old value
+                AssociatedObject.Text = _oldText;
+            _valueConverted = true;
         }
         }
     }
     }
-}
+}

+ 4 - 12
PixiEditor/Helpers/Converters/BoolToColorConverter.cs

@@ -8,23 +8,15 @@ namespace PixiEditor.Helpers.Converters
     {
     {
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if(value.ToString() == "Transparent")
-            {
-                return false;
-            }
-            return true;
+            return value?.ToString() == "Transparent";
         }
         }
 
 
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if(value is bool)
-            {
-                if((bool)value == false)
-                {
+            if (value is bool boolean)
+                if (boolean == false)
                     return "Transparent";
                     return "Transparent";
-                }
-            }
             return "#638DCA";
             return "#638DCA";
         }
         }
     }
     }
-}
+}

+ 4 - 14
PixiEditor/Helpers/Converters/BoolToIntConverter.cs

@@ -1,7 +1,5 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
-using System.Text;
 using System.Windows.Data;
 using System.Windows.Data;
 
 
 namespace PixiEditor.Helpers.Converters
 namespace PixiEditor.Helpers.Converters
@@ -10,23 +8,15 @@ namespace PixiEditor.Helpers.Converters
     {
     {
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if (value.ToString() == "0")
-            {
-                return false;
-            }
-            return true;
+            return value.ToString() == "0";
         }
         }
 
 
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if (value is bool)
-            {
-                if ((bool)value == false)
-                {
+            if (value is bool boolean)
+                if (boolean == false)
                     return 0;
                     return 0;
-                }
-            }
             return 1;
             return 1;
         }
         }
     }
     }
-}
+}

+ 21 - 0
PixiEditor/Helpers/Converters/BoolToInvertedBoolConverter.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class BoolToInvertedBoolConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is bool boolValue)
+                return !boolValue;
+            return false;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException("ConvertBack() of BoolToInvertedBoolConverter is not implemented");
+        }
+    }
+}

+ 24 - 0
PixiEditor/Helpers/Converters/ColorToBrushConverter.cs

@@ -0,0 +1,24 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class ColorToBrushConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            Color col = (Color) value;
+            Color c = Color.FromArgb(col.A, col.R, col.G, col.B);
+            return new SolidColorBrush(c);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            SolidColorBrush c = (SolidColorBrush) value;
+            Color col = Color.FromArgb(c.Color.A, c.Color.R, c.Color.G, c.Color.B);
+            return col;
+        }
+    }
+}

+ 17 - 0
PixiEditor/Helpers/Converters/FloatNormalizeConverter.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class FloatNormalizeConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => 
+            (float)value * 100;
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => 
+            (float) value / 100;
+    }
+}

+ 6 - 15
PixiEditor/Helpers/Converters/OppositeVisibilityConverter.cs

@@ -1,7 +1,5 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
-using System.Text;
 using System.Windows;
 using System.Windows;
 using System.Windows.Data;
 using System.Windows.Data;
 
 
@@ -11,27 +9,20 @@ namespace PixiEditor.Helpers.Converters
     {
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if(value.ToString().ToLower() == "visible")
-            {
-                return Visibility.Hidden;
-            }
+            if (value.ToString().ToLower() == "visible") return Visibility.Hidden;
             return Visibility.Visible;
             return Visibility.Visible;
         }
         }
 
 
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
-            if(value is Visibility)
+            if (value is Visibility)
             {
             {
-                if((Visibility)value == Visibility.Visible)
-                {
+                if ((Visibility) value == Visibility.Visible)
                     return "Hidden";
                     return "Hidden";
-                }
-                else
-                {
-                    return "Visible";
-                }
+                return "Visible";
             }
             }
+
             return null;
             return null;
         }
         }
     }
     }
-}
+}

+ 2 - 5
PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -1,16 +1,13 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
-using System.Text;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
-using System.Threading.Tasks;
 using System.Windows.Data;
 using System.Windows.Data;
 
 
 namespace PixiEditor.Helpers
 namespace PixiEditor.Helpers
 {
 {
     [ValueConversion(typeof(string), typeof(int))]
     [ValueConversion(typeof(string), typeof(int))]
-    class ToolSizeToIntConverter : IValueConverter
+    internal class ToolSizeToIntConverter : IValueConverter
     {
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
@@ -26,4 +23,4 @@ namespace PixiEditor.Helpers
             return int.Parse(slicedString);
             return int.Parse(slicedString);
         }
         }
     }
     }
-}
+}

+ 6 - 11
PixiEditor/Helpers/Extensions/DictionaryHelper.cs

@@ -4,23 +4,18 @@ namespace PixiEditor.Helpers.Extensions
 {
 {
     public static class DictionaryHelper
     public static class DictionaryHelper
     {
     {
-        public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dict, IDictionary<TKey, TValue> dictToAdd)
+        public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dict,
+            IDictionary<TKey, TValue> dictToAdd)
         {
         {
-            foreach (var item in dictToAdd)
-            {
-                dict[item.Key] = item.Value;
-            }
+            foreach (var item in dictToAdd) dict[item.Key] = item.Value;
         }
         }
 
 
-        public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dict, IDictionary<TKey, TValue> dictToAdd)
+        public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dict,
+            IDictionary<TKey, TValue> dictToAdd)
         {
         {
             foreach (var item in dictToAdd)
             foreach (var item in dictToAdd)
-            {
                 if (!dict.ContainsKey(item.Key))
                 if (!dict.ContainsKey(item.Key))
-                {
                     dict.Add(item.Key, item.Value);
                     dict.Add(item.Key, item.Value);
-                }
-            }
         }
         }
     }
     }
-}
+}

+ 3 - 10
PixiEditor/Helpers/NotifyableObject.cs

@@ -1,23 +1,16 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 
 namespace PixiEditor.Helpers
 namespace PixiEditor.Helpers
 {
 {
     [Serializable]
     [Serializable]
     public class NotifyableObject : INotifyPropertyChanged
     public class NotifyableObject : INotifyPropertyChanged
     {
     {
-        public event PropertyChangedEventHandler PropertyChanged = delegate { };
+        [field: NonSerialized] public event PropertyChangedEventHandler PropertyChanged = delegate { };
 
 
         protected void RaisePropertyChanged(string property)
         protected void RaisePropertyChanged(string property)
         {
         {
-            if (property != null)
-            {
-                PropertyChanged(this, new PropertyChangedEventArgs(property));
-            }
+            if (property != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
         }
         }
     }
     }
-}
+}

+ 6 - 9
PixiEditor/Helpers/RelayCommand.cs

@@ -1,8 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
 namespace PixiEditor.Helpers
 namespace PixiEditor.Helpers
@@ -11,8 +7,8 @@ namespace PixiEditor.Helpers
     {
     {
         #region Fields
         #region Fields
 
 
-        readonly Action<object> _execute;
-        readonly Predicate<object> _canExecute;
+        private readonly Action<object> _execute;
+        private readonly Predicate<object> _canExecute;
 
 
         #endregion // Fields
         #endregion // Fields
 
 
@@ -31,6 +27,7 @@ namespace PixiEditor.Helpers
             _execute = execute;
             _execute = execute;
             _canExecute = canExecute;
             _canExecute = canExecute;
         }
         }
+
         #endregion // Constructors
         #endregion // Constructors
 
 
         #region ICommand Members
         #region ICommand Members
@@ -42,8 +39,8 @@ namespace PixiEditor.Helpers
 
 
         public event EventHandler CanExecuteChanged
         public event EventHandler CanExecuteChanged
         {
         {
-            add { CommandManager.RequerySuggested += value; }
-            remove { CommandManager.RequerySuggested -= value; }
+            add => CommandManager.RequerySuggested += value;
+            remove => CommandManager.RequerySuggested -= value;
         }
         }
 
 
         public void Execute(object parameter)
         public void Execute(object parameter)
@@ -53,4 +50,4 @@ namespace PixiEditor.Helpers
 
 
         #endregion // ICommand Members
         #endregion // ICommand Members
     }
     }
-}
+}

+ 3 - 4
PixiEditor/Helpers/UI/ReversedOrderStackPanel.cs

@@ -1,5 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Windows;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
@@ -8,9 +7,9 @@ namespace PixiEditor.Helpers.UI
 {
 {
     public class ReversedOrderStackPanel : StackPanel
     public class ReversedOrderStackPanel : StackPanel
     {
     {
-        protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
+        protected override Size ArrangeOverride(Size arrangeSize)
         {
         {
-            bool fHorizontal = (Orientation == Orientation.Horizontal);
+            bool fHorizontal = Orientation == Orientation.Horizontal;
             var rcChild = new Rect(arrangeSize);
             var rcChild = new Rect(arrangeSize);
             double previousChildSize = 0.0;
             double previousChildSize = 0.0;
 
 
@@ -41,4 +40,4 @@ namespace PixiEditor.Helpers.UI
             return arrangeSize;
             return arrangeSize;
         }
         }
     }
     }
-}
+}

+ 76 - 0
PixiEditor/Helpers/UI/RgbColorSlider.cs

@@ -0,0 +1,76 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.UI
+{
+    public class RgbColorSlider : Slider
+    {
+        // Using a DependencyProperty as the backing store for SliderArgbType.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty SliderArgbTypeProperty =
+            DependencyProperty.Register("SliderArgbType", typeof(string), typeof(RgbColorSlider),
+                new PropertyMetadata(""));
+
+        // Using a DependencyProperty as the backing store for CurrentColor.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty CurrentColorProperty =
+            DependencyProperty.Register("CurrentColor", typeof(Color), typeof(RgbColorSlider),
+                new PropertyMetadata(Colors.Black, ColorChangedCallback));
+
+        public RgbColorSlider()
+        {
+            Minimum = 0;
+            Maximum = 255;
+            SmallChange = 1;
+            LargeChange = 10;
+            MinHeight = 12;
+        }
+
+
+        public string SliderArgbType
+        {
+            get => (string) GetValue(SliderArgbTypeProperty);
+            set => SetValue(SliderArgbTypeProperty, value);
+        }
+
+
+        public Color CurrentColor
+        {
+            get => (Color) GetValue(CurrentColorProperty);
+            set => SetValue(CurrentColorProperty, value);
+        }
+
+        public override void EndInit()
+        {
+            base.EndInit();
+            GenerateBackground();
+        }
+
+
+        private void GenerateBackground()
+        {
+            Background = new LinearGradientBrush(new GradientStopCollection
+            {
+                new GradientStop(GetColorForSelectedArgb(0), 0.0),
+                new GradientStop(GetColorForSelectedArgb(255), 1)
+            });
+        }
+
+        private Color GetColorForSelectedArgb(byte value)
+        {
+            return SliderArgbType switch
+            {
+                "A" => Color.FromArgb(value, CurrentColor.R, CurrentColor.G, CurrentColor.B),
+                "R" => Color.FromArgb(CurrentColor.A, value, CurrentColor.G, CurrentColor.B),
+                "G" => Color.FromArgb(CurrentColor.A, CurrentColor.R, value, CurrentColor.B),
+                "B" => Color.FromArgb(CurrentColor.A, CurrentColor.R, CurrentColor.G, value),
+                _ => CurrentColor
+            };
+        }
+
+        private static void ColorChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            RgbColorSlider slider = (RgbColorSlider) d;
+            slider.GenerateBackground();
+        }
+    }
+}

+ 13 - 0
PixiEditor/Helpers/Validators/SizeValidationRule.cs

@@ -0,0 +1,13 @@
+using System.Globalization;
+using System.Windows.Controls;
+
+namespace PixiEditor.Helpers.Validators
+{
+    public class SizeValidationRule : ValidationRule
+    {
+        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
+        {
+            return new ValidationResult(int.Parse(((string) value).Split(' ')[0]) > 0, null); //Size is greater than 0
+        }
+    }
+}

BIN
PixiEditor/Images/AnchorDot.png


BIN
PixiEditor/Images/ColorCircle.png


BIN
PixiEditor/Images/ColorPalette.png


BIN
PixiEditor/Images/Cross.png


BIN
PixiEditor/Images/MoveImage.png


BIN
PixiEditor/Images/PixiEditorLogo.png


BIN
PixiEditor/Images/SelectImage.png


+ 31 - 34
PixiEditor/Models/Colors/ExColor.cs

@@ -6,13 +6,15 @@ namespace PixiEditor.Models.Colors
     public static class ExColor
     public static class ExColor
     {
     {
         /// <summary>
         /// <summary>
-        /// Creates color with corrected brightness.
+        ///     Creates color with corrected brightness.
         /// </summary>
         /// </summary>
         /// <param name="color">Color to correct.</param>
         /// <param name="color">Color to correct.</param>
-        /// <param name="correctionFactor">The brightness correction factor. Must be between -1 and 1. 
-        /// Negative values produce darker colors.</param>
+        /// <param name="correctionFactor">
+        ///     The brightness correction factor. Must be between -1 and 1.
+        ///     Negative values produce darker colors.
+        /// </param>
         /// <returns>
         /// <returns>
-        /// Corrected <see cref="Color"/> structure.
+        ///     Corrected <see cref="Color" /> structure.
         /// </returns>
         /// </returns>
         public static Color ChangeColorBrightness(Color color, float correctionFactor)
         public static Color ChangeColorBrightness(Color color, float correctionFactor)
         {
         {
@@ -29,19 +31,19 @@ namespace PixiEditor.Models.Colors
 
 
 
 
         /// <summary>
         /// <summary>
-        /// Converts RGB to HSL
+        ///     Converts RGB to HSL
         /// </summary>
         /// </summary>
         /// <param name="r">Red value</param>
         /// <param name="r">Red value</param>
         /// <param name="b">Blue value</param>
         /// <param name="b">Blue value</param>
         /// <param name="g">Green value</param>
         /// <param name="g">Green value</param>
         /// <returns>Tuple with 3 values in order: h, s, l0</returns>
         /// <returns>Tuple with 3 values in order: h, s, l0</returns>
-        public static Tuple<int,float,float> RgbToHsl(int r, int g, int b)
+        public static Tuple<int, float, float> RgbToHsl(int r, int g, int b)
         {
         {
             int h;
             int h;
             float s, l;
             float s, l;
-            float dR = (r / 255.0f);
-            float dG = (g / 255.0f);
-            float dB = (b / 255.0f);
+            float dR = r / 255.0f;
+            float dG = g / 255.0f;
+            float dB = b / 255.0f;
 
 
             float min = Math.Min(Math.Min(dR, dG), dB);
             float min = Math.Min(Math.Min(dR, dG), dB);
             float max = Math.Max(Math.Max(dR, dG), dB);
             float max = Math.Max(Math.Max(dR, dG), dB);
@@ -56,35 +58,30 @@ namespace PixiEditor.Models.Colors
             }
             }
             else
             else
             {
             {
-                s = (l <= 0.5) ? (delta / (max + min)) : (delta / (2 - max - min));
+                s = l <= 0.5 ? delta / (max + min) : delta / (2 - max - min);
 
 
                 float hue;
                 float hue;
 
 
                 if (dR == max)
                 if (dR == max)
-                {
-                    hue = ((dG - dB) / 6) / delta;
-                }
+                    hue = (dG - dB) / 6 / delta;
                 else if (dG == max)
                 else if (dG == max)
-                {
-                    hue = (1.0f / 3) + (dB - dR) / 6 / delta;
-                }
+                    hue = 1.0f / 3 + (dB - dR) / 6 / delta;
                 else
                 else
-                {
-                    hue = (2.0f / 3) + (dR - dG) / 6 / delta;
-                }
+                    hue = 2.0f / 3 + (dR - dG) / 6 / delta;
 
 
                 if (hue < 0)
                 if (hue < 0)
                     hue += 1;
                     hue += 1;
                 if (hue > 1)
                 if (hue > 1)
                     hue -= 1;
                     hue -= 1;
 
 
-                h = (int)(hue * 360);
+                h = (int) (hue * 360);
             }
             }
+
             return new Tuple<int, float, float>(h, s * 100, l * 100);
             return new Tuple<int, float, float>(h, s * 100, l * 100);
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Converts HSL color format to RGB
+        ///     Converts HSL color format to RGB
         /// </summary>
         /// </summary>
         /// <param name="h"></param>
         /// <param name="h"></param>
         /// <param name="s"></param>
         /// <param name="s"></param>
@@ -100,20 +97,21 @@ namespace PixiEditor.Models.Colors
 
 
             if (s == 0)
             if (s == 0)
             {
             {
-                r = g = b = (byte)(l * 255);
+                r = g = b = (byte) (l * 255);
             }
             }
             else
             else
             {
             {
                 float v1, v2;
                 float v1, v2;
-                float hue = (float)h / 360;
+                float hue = (float) h / 360;
 
 
-                v2 = (l < 0.5) ? (l * (1 + s)) : (l + s - (l * s));
+                v2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
                 v1 = 2 * l - v2;
                 v1 = 2 * l - v2;
 
 
-                r = (byte)(255 * HueToRGB(v1, v2, hue + (1.0f / 3)));
-                g = (byte)(255 * HueToRGB(v1, v2, hue));
-                b = (byte)(255 * HueToRGB(v1, v2, hue - (1.0f / 3)));
+                r = (byte) (255 * HueToRGB(v1, v2, hue + 1.0f / 3));
+                g = (byte) (255 * HueToRGB(v1, v2, hue));
+                b = (byte) (255 * HueToRGB(v1, v2, hue - 1.0f / 3));
             }
             }
+
             return Color.FromRgb(r, g, b);
             return Color.FromRgb(r, g, b);
         }
         }
 
 
@@ -125,17 +123,16 @@ namespace PixiEditor.Models.Colors
             if (hue > 1)
             if (hue > 1)
                 hue -= 1;
                 hue -= 1;
 
 
-            if ((6 * hue) < 1)
-                return (v1 + (v2 - v1) * 6 * hue);
+            if (6 * hue < 1)
+                return v1 + (v2 - v1) * 6 * hue;
 
 
-            if ((2 * hue) < 1)
+            if (2 * hue < 1)
                 return v2;
                 return v2;
 
 
-            if ((3 * hue) < 2)
-                return (v1 + (v2 - v1) * ((2.0f / 3) - hue) * 6);
+            if (3 * hue < 2)
+                return v1 + (v2 - v1) * (2.0f / 3 - hue) * 6;
 
 
             return v1;
             return v1;
         }
         }
-
     }
     }
-}
+}

+ 249 - 0
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -0,0 +1,249 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Events;
+using PixiEditor.Models.Images;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class BitmapManager : NotifyableObject
+    {
+        public MouseMovementController MouseController { get; set; }
+
+        public Tool SelectedTool
+        {
+            get => _selectedTool;
+            private set
+            {
+                _selectedTool = value;
+                RaisePropertyChanged("SelectedTool");
+            }
+        }
+
+        public Layer PreviewLayer
+        {
+            get => _previewLayer;
+            set
+            {
+                _previewLayer = value;
+                RaisePropertyChanged("PreviewLayer");
+            }
+        }
+
+        public Layer ActiveLayer => ActiveDocument.ActiveLayer;
+
+        public Color PrimaryColor { get; set; }
+
+        public int ToolSize => SelectedTool.Toolbar.GetSetting("ToolSize") != null
+            ? (int) SelectedTool.Toolbar.GetSetting("ToolSize").Value
+            : 1;
+
+        public BitmapOperationsUtility BitmapOperations { get; set; }
+        public ReadonlyToolUtility ReadonlyToolUtility { get; set; }
+
+        public Document ActiveDocument
+        {
+            get => _activeDocument;
+            set
+            {
+                _activeDocument = value;
+                RaisePropertyChanged("ActiveDocument");
+                DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value));
+            }
+        }
+
+        private Document _activeDocument;
+
+        private Layer _previewLayer;
+        private Tool _selectedTool;
+
+        public BitmapManager()
+        {
+            MouseController = new MouseMovementController();
+            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
+            MouseController.MousePositionChanged += Controller_MousePositionChanged;
+            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
+            BitmapOperations = new BitmapOperationsUtility(this);
+            ReadonlyToolUtility = new ReadonlyToolUtility(this);
+        }
+
+        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+        public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
+
+        public void SetActiveTool(Tool tool)
+        {
+            PreviewLayer = null;
+            SelectedTool?.Toolbar.SaveToolbarSettings();
+            SelectedTool = tool;
+            SelectedTool.Toolbar.LoadSharedSettings();
+        }
+
+        public void SetActiveLayer(int index)
+        {
+            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
+                ActiveDocument.ActiveLayer.IsActive = false;
+            ActiveDocument.ActiveLayerIndex = index;
+            ActiveDocument.ActiveLayer.IsActive = true;
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
+        }
+
+        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        {
+            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
+        }
+
+        public void AddNewLayer(string name, bool setAsActive = true)
+        {
+            AddNewLayer(name, 0, 0, setAsActive);
+        }
+
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        {
+            ActiveDocument.Layers.Add(new Layer(name, width, height)
+            {
+                MaxHeight = ActiveDocument.Height,
+                MaxWidth = ActiveDocument.Width
+            });
+            if (setAsActive) SetActiveLayer(ActiveDocument.Layers.Count - 1);
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
+        public void RemoveLayer(int layerIndex)
+        {
+            if (ActiveDocument.Layers.Count == 0) return;
+
+            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
+            ActiveDocument.Layers.RemoveAt(layerIndex);
+            if (wasActive)
+                SetActiveLayer(0);
+            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
+                SetActiveLayer(ActiveDocument.Layers.Count - 1);
+        }
+
+        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
+        {
+            if (Mouse.LeftButton == MouseButtonState.Pressed && !IsDraggingViewport()
+                                                             && MouseController.ClickedOnCanvas && ActiveDocument != null)
+            {
+                if (IsOperationTool(SelectedTool))
+                    BitmapOperations.ExecuteTool(e.NewPosition,
+                        MouseController.LastMouseMoveCoordinates.ToList(), (BitmapOperationTool) SelectedTool);
+                else
+                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates.ToArray(),
+                        (ReadonlyTool) SelectedTool);
+            }
+            else if (Mouse.LeftButton == MouseButtonState.Released)
+            {
+                HighlightPixels(e.NewPosition);
+            }
+        }
+
+        private bool IsDraggingViewport()
+        {
+            return Keyboard.IsKeyDown(Key.LeftShift);
+        }
+
+        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnMouseDown();
+            PreviewLayer = null;
+        }
+
+        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
+        {
+            SelectedTool.OnMouseUp();
+            if (IsOperationTool(SelectedTool) && ((BitmapOperationTool) SelectedTool).RequiresPreviewLayer)
+                BitmapOperations.StopAction();
+        }
+
+        public void GeneratePreviewLayer()
+        {
+            if (ActiveDocument != null)
+                PreviewLayer = new Layer("_previewLayer")
+                {
+                    MaxWidth = ActiveDocument.Width,
+                    MaxHeight = ActiveDocument.Height
+                };
+        }
+
+        private void HighlightPixels(Coordinates newPosition)
+        {
+            if (ActiveDocument == null || ActiveDocument.Layers.Count == 0 || SelectedTool.HideHighlight) return;
+            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
+                CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
+            if (CanChangeHighlightOffset(highlightArea))
+            {
+                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y,0,0);
+            }
+            else if (!IsInsideBounds(highlightArea))
+            {
+                PreviewLayer = null;
+            }
+            else
+            {
+                GeneratePreviewLayer();
+                PreviewLayer.ApplyPixels(
+                    BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
+            }
+        }
+
+        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
+        {
+            return highlightArea.Length > 0 && PreviewLayer != null && 
+                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
+        }
+
+        private bool IsInsideBounds(Coordinates[] highlightArea)
+        {
+            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
+                    highlightArea[0].Y <= ActiveDocument.Height - 1 &&
+                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
+        }
+
+        public WriteableBitmap GetCombinedLayersBitmap()
+        {
+            return BitmapUtils.CombineLayers(ActiveDocument.Layers.ToArray());
+        }
+
+        /// <summary>
+        ///     Returns if selected tool is BitmapOperationTool
+        /// </summary>
+        /// <returns></returns>
+        public bool IsOperationTool()
+        {
+            return IsOperationTool(SelectedTool);
+        }
+
+        /// <summary>
+        ///     Returns if tool is BitmapOperationTool
+        /// </summary>
+        /// <param name="tool"></param>
+        /// <returns></returns>
+        public static bool IsOperationTool(Tool tool)
+        {
+            return tool is BitmapOperationTool;
+        }
+    }
+
+    public class LayersChangedEventArgs : EventArgs
+    {
+        public int LayerAffected { get; set; }
+        public LayerAction LayerChangeType { get; set; }
+
+        public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
+        {
+            LayerAffected = layerAffected;
+            LayerChangeType = layerChangeType;
+        }
+    }
+}

+ 84 - 219
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -1,158 +1,92 @@
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using System.Windows.Input;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Images;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
 
 
 namespace PixiEditor.Models.Controllers
 namespace PixiEditor.Models.Controllers
 {
 {
-    public class BitmapOperationsUtility : NotifyableObject
+    public class BitmapOperationsUtility
     {
     {
-        public MouseMovementController MouseController { get; set; }
-        private Tool _selectedTool;
-        public Tool SelectedTool
-        {
-            get => _selectedTool;
-            private set
-            {                
-                _selectedTool = value;
-                RaisePropertyChanged("SelectedTool");
-            }
-        }
+        public BitmapManager Manager { get; set; }
+        private LayerChange[] _lastModifiedLayers;
 
 
-        private ObservableCollection<Layer> _layers = new ObservableCollection<Layer>();
+        private Coordinates _lastMousePos;
 
 
-        public ObservableCollection<Layer> Layers
-        {
-            get => _layers;
-            set { if (_layers != value) { _layers = value; } }
-        }
-        private int _activeLayerIndex;
-        public int ActiveLayerIndex
+        public BitmapOperationsUtility(BitmapManager manager)
         {
         {
-            get => _activeLayerIndex;
-            set
-            {
-                _activeLayerIndex = value;
-                RaisePropertyChanged("ActiveLayerIndex");
-                RaisePropertyChanged("ActiveLayer");
-            }
+            Manager = manager;
         }
         }
 
 
-        private Layer _previewLayer;
+        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
 
 
-        public Layer PreviewLayer
+        public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
         {
-            get { return _previewLayer; }
-            set 
+            var changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
+            var 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++)
             {
             {
-                _previewLayer = value;
-                RaisePropertyChanged("PreviewLayer");
+                old[i] = new LayerChange(
+                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i]]), i);
+                newChange[i] = new LayerChange(changes, i);
+                layers[i].ApplyPixels(changes);
             }
             }
-        }
-
-
-        public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
-
-        public Color PrimaryColor { get; set; }
-
-        public int ToolSize => SelectedTool.Toolbar.GetSetting("ToolSize") != null ? (int)SelectedTool.Toolbar.GetSetting("ToolSize").Value : 1;
-
-        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
-
-        private Coordinates _lastMousePos;
-        private BitmapPixelChanges _lastChangedPixels;
 
 
-        public BitmapOperationsUtility()
-        {
-            MouseController = new MouseMovementController();
-            MouseController.StartedRecordingChanges += MouseController_StartedRecordingChanges;
-            MouseController.MousePositionChanged += Controller_MousePositionChanged;
-            MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
+            UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
         }
         }
 
 
-        public void SetActiveTool(Tool tool)
+        public void ExecuteTool(Coordinates newPos, List<Coordinates> mouseMove, BitmapOperationTool tool)
         {
         {
-            if(PreviewLayer != null)
-            {
-                PreviewLayer.Clear();
-            }
-            if (SelectedTool != null)
+            if (Manager.ActiveDocument != null && tool != null && tool.ToolType != ToolType.None)
             {
             {
-                SelectedTool.Toolbar.SaveToolbarSettings();
-            }
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
-        }
+                if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0) return;
+                mouseMove.Reverse();
+                UseTool(mouseMove, tool, Manager.PrimaryColor);
 
 
-        private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
-        {
-            if (PreviewLayer != null)
-            {
-                PreviewLayer.Clear();
+                _lastMousePos = newPos;
             }
             }
         }
         }
 
 
-        private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
+        public void StopAction()
         {
         {
-            if(SelectedTool.RequiresPreviewLayer)
+            if (_lastModifiedLayers == null) return;
+            for (int i = 0; i < _lastModifiedLayers.Length; i++)
             {
             {
-                BitmapPixelChanges oldValues = GetOldPixelsValues(_lastChangedPixels.ChangedPixels.Keys.ToArray());
-                Layers[ActiveLayerIndex].ApplyPixels(_lastChangedPixels);
-                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(_lastChangedPixels, oldValues, ActiveLayerIndex));
-                _previewLayer.Clear();
-            }
-        }
+                var layer = Manager.ActiveDocument.Layers[_lastModifiedLayers[i].LayerIndex];
 
 
-        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
-        {
-            if(SelectedTool != null && SelectedTool.ToolType != ToolType.None && Mouse.LeftButton == MouseButtonState.Pressed)
-            {
-                var mouseMove = MouseController.LastMouseMoveCoordinates.ToList();
-                if (Layers.Count == 0 || mouseMove.Count == 0) return;
-                mouseMove.Reverse();
-                UseTool(mouseMove);
-                
-                _lastMousePos = e.NewPosition;
-            }
-            else if(Mouse.LeftButton == MouseButtonState.Released)
-            {
-                HighlightPixels(e.NewPosition);
+                BitmapPixelChanges oldValues = ApplyToLayer(layer, _lastModifiedLayers[i]).PixelChanges;
+
+               BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(_lastModifiedLayers[i].PixelChanges,
+                    oldValues, _lastModifiedLayers[i].LayerIndex));
+                Manager.PreviewLayer.Clear();
             }
             }
         }
         }
 
 
-        private void HighlightPixels(Coordinates newPosition)
-        {
-            if (Layers.Count == 0 || SelectedTool.HideHighlight) return;
-            GeneratePreviewLayer();
-            PreviewLayer.Clear();
-            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));    
-            PreviewLayer.ApplyPixels(BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77,0,0,0)));
-        }
 
 
-        private void UseTool(List<Coordinates> mouseMoveCords)
+        private void UseTool(List<Coordinates> mouseMoveCords, BitmapOperationTool tool, Color color)
         {
         {
-            if (SelectedTool.PerformsOperationOnBitmap == false) return;
             if (Keyboard.IsKeyDown(Key.LeftShift) && !MouseCordsNotInLine(mouseMoveCords))
             if (Keyboard.IsKeyDown(Key.LeftShift) && !MouseCordsNotInLine(mouseMoveCords))
-            {
                 mouseMoveCords = GetSquareCoordiantes(mouseMoveCords);
                 mouseMoveCords = GetSquareCoordiantes(mouseMoveCords);
-            }
-            if (!SelectedTool.RequiresPreviewLayer)
+            if (!tool.RequiresPreviewLayer)
             {
             {
-                BitmapPixelChanges changedPixels = SelectedTool.Use(Layers[ActiveLayerIndex], mouseMoveCords.ToArray(), PrimaryColor);
-                BitmapPixelChanges oldPixelsValues = GetOldPixelsValues(changedPixels.ChangedPixels.Keys.ToArray());
-                ActiveLayer.ApplyPixels(changedPixels);
-                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(changedPixels, oldPixelsValues, ActiveLayerIndex));
+                LayerChange[] modifiedLayers = tool.Use(Manager.ActiveLayer, mouseMoveCords.ToArray(), color);
+                LayerChange[] oldPixelsValues = new LayerChange[modifiedLayers.Length];
+                for (int i = 0; i < modifiedLayers.Length; i++)
+                {
+                    var layer = Manager.ActiveDocument.Layers[modifiedLayers[i].LayerIndex];
+                    oldPixelsValues[i] = ApplyToLayer(layer, modifiedLayers[i]);
+                    
+                    BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(modifiedLayers[i].PixelChanges,
+                        oldPixelsValues[i].PixelChanges, modifiedLayers[i].LayerIndex));
+                }
             }
             }
             else
             else
             {
             {
@@ -160,13 +94,25 @@ namespace PixiEditor.Models.Controllers
             }
             }
         }
         }
 
 
+        private LayerChange ApplyToLayer(Layer layer, LayerChange change)
+        {
+            layer.DynamicResize(change.PixelChanges);
+
+            var oldPixelsValues = new LayerChange(
+                GetOldPixelsValues(change.PixelChanges.ChangedPixels.Keys.ToArray()),
+                change.LayerIndex);
+
+            layer.ApplyPixels(change.PixelChanges, false);
+            return oldPixelsValues;
+        }
+
         private bool MouseCordsNotInLine(List<Coordinates> cords)
         private bool MouseCordsNotInLine(List<Coordinates> cords)
         {
         {
             return cords[0].X == cords[^1].X || cords[0].Y == cords[^1].Y;
             return cords[0].X == cords[^1].X || cords[0].Y == cords[^1].Y;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Extracts square from rectangle mouse drag, used to draw symmetric shapes.
+        ///     Extracts square from rectangle mouse drag, used to draw symmetric shapes.
         /// </summary>
         /// </summary>
         /// <param name="mouseMoveCords"></param>
         /// <param name="mouseMoveCords"></param>
         /// <returns></returns>
         /// <returns></returns>
@@ -174,14 +120,8 @@ namespace PixiEditor.Models.Controllers
         {
         {
             int xLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
             int xLength = mouseMoveCords[0].Y - mouseMoveCords[^1].Y;
             int yLength = 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;
-            }
+            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);
             mouseMoveCords[0] = new Coordinates(mouseMoveCords[^1].X + xLength, mouseMoveCords[^1].Y + yLength);
             return mouseMoveCords;
             return mouseMoveCords;
         }
         }
@@ -189,96 +129,32 @@ namespace PixiEditor.Models.Controllers
         private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
         private BitmapPixelChanges GetOldPixelsValues(Coordinates[] coordinates)
         {
         {
             Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
             Dictionary<Coordinates, Color> values = new Dictionary<Coordinates, Color>();
-            Layers[ActiveLayerIndex].LayerBitmap.Lock();
-            for (int i = 0; i < coordinates.Length; i++)
+            using (Manager.ActiveLayer.LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
             {
             {
-                if (coordinates[i].X < 0 || coordinates[i].X > Layers[0].Width - 1 || coordinates[i].Y < 0 || coordinates[i].Y > Layers[0].Height - 1) 
-                    continue;
-                values.Add(coordinates[i], Layers[ActiveLayerIndex].LayerBitmap.GetPixel(coordinates[i].X, coordinates[i].Y));
+                var relativeCoords = Manager.ActiveLayer.ConvertToRelativeCoordinates(coordinates);
+                for (int i = 0; i < coordinates.Length; i++)
+                {
+                    values.Add(coordinates[i],
+                        Manager.ActiveLayer.GetPixel(relativeCoords[i].X, relativeCoords[i].Y));
+                }
+
             }
             }
-            Layers[ActiveLayerIndex].LayerBitmap.Unlock();
             return new BitmapPixelChanges(values);
             return new BitmapPixelChanges(values);
         }
         }
 
 
         private void UseToolOnPreviewLayer(List<Coordinates> mouseMove)
         private void UseToolOnPreviewLayer(List<Coordinates> mouseMove)
         {
         {
-            BitmapPixelChanges changedPixels;
+            LayerChange[] modifiedLayers;
             if (mouseMove.Count > 0 && mouseMove[0] != _lastMousePos)
             if (mouseMove.Count > 0 && mouseMove[0] != _lastMousePos)
             {
             {
-                GeneratePreviewLayer();
-                PreviewLayer.Clear();
-                changedPixels = SelectedTool.Use(Layers[ActiveLayerIndex], mouseMove.ToArray(), PrimaryColor);
-                PreviewLayer.ApplyPixels(changedPixels);
-                _lastChangedPixels = changedPixels;
-            }
-        }
-
-        private void GeneratePreviewLayer()
-        {
-            if (PreviewLayer == null)
-            {
-                PreviewLayer = new Layer("_previewLayer", Layers[0].Width, Layers[0].Height);
-            }
-        }
-
-        public void RemoveLayer(int layerIndex)
-        {
-            if (Layers.Count <= 1) return;
-
-            bool wasActive = Layers[layerIndex].IsActive;
-            Layers.RemoveAt(layerIndex);
-            if (wasActive)
-            {
-                SetActiveLayer(0);
-            }
-            else if(ActiveLayerIndex > Layers.Count - 1)
-            {
-                SetActiveLayer(Layers.Count - 1);
+                Manager.GeneratePreviewLayer();
+                modifiedLayers = ((BitmapOperationTool) Manager.SelectedTool).Use(Manager.ActiveDocument.ActiveLayer,
+                    mouseMove.ToArray(), Manager.PrimaryColor);
+                BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
+                Manager.PreviewLayer.ApplyPixels(BitmapPixelChanges.CombineOverride(changes));
+                _lastModifiedLayers = modifiedLayers;
             }
             }
         }
         }
-
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
-        {
-            Layers.Add(new Layer(name, width, height));
-            if (setAsActive)
-            {
-                SetActiveLayer(Layers.Count - 1);
-            }
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
-        }
-
-        public void SetActiveLayer(int index)
-        {
-            if (ActiveLayerIndex <= Layers.Count - 1)
-            {
-                Layers[ActiveLayerIndex].IsActive = false;
-            }
-                ActiveLayerIndex = index;
-            Layers[ActiveLayerIndex].IsActive = true;
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
-        }
-
-        public WriteableBitmap GetCombinedLayersBitmap()
-        {
-            WriteableBitmap finalBitmap = Layers[0].LayerBitmap.Clone();
-            finalBitmap.Lock();
-            for (int i = 1; i < Layers.Count; i++)
-            {
-                for (int y = 0; y < finalBitmap.Height; y++)
-                {
-                    for (int x = 0; x < finalBitmap.Width; x++)
-                    {
-                        Color color = Layers[i].LayerBitmap.GetPixel(x, y);
-                        if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0)
-                        {
-                            finalBitmap.SetPixel(x, y, color);
-                        }
-                    }
-                }
-            }
-            finalBitmap.Unlock();
-            return finalBitmap;
-        }
     }
     }
 }
 }
 
 
@@ -288,22 +164,11 @@ public class BitmapChangedEventArgs : EventArgs
     public BitmapPixelChanges OldPixelsValues { get; set; }
     public BitmapPixelChanges OldPixelsValues { get; set; }
     public int ChangedLayerIndex { get; set; }
     public int ChangedLayerIndex { get; set; }
 
 
-    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues, int changedLayerIndex)
+    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, BitmapPixelChanges oldPixelsValues,
+        int changedLayerIndex)
     {
     {
         PixelsChanged = pixelsChanged;
         PixelsChanged = pixelsChanged;
         OldPixelsValues = oldPixelsValues;
         OldPixelsValues = oldPixelsValues;
         ChangedLayerIndex = changedLayerIndex;
         ChangedLayerIndex = changedLayerIndex;
     }
     }
-}
-
-public class LayersChangedEventArgs : EventArgs
-{
-    public int LayerAffected { get; set; }
-    public LayerAction LayerChangeType { get; set; }
-
-    public LayersChangedEventArgs(int layerAffected, LayerAction layerChangeType)
-    {
-        LayerAffected = layerAffected;
-        LayerChangeType = layerChangeType;
-    }
-}
+}

+ 83 - 0
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -0,0 +1,83 @@
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Images;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class ClipboardController
+    {
+        /// <summary>
+        ///     Copies selection to clipboard in PNG, Bitmap and DIB formats.
+        /// </summary>
+        /// <param name="layers">Layers where selection is</param>
+        /// <param name="selection"></param>
+        public void CopyToClipboard(Layer[] layers, Coordinates[] selection)
+        {
+            Clipboard.Clear();
+            WriteableBitmap combinedBitmaps = BitmapUtils.CombineLayers(layers);
+            using (var pngStream = new MemoryStream())
+            {
+                DataObject data = new DataObject();
+                var croppedBmp = BitmapSelectionToBmpSource(combinedBitmaps, selection);
+                data.SetData(DataFormats.Bitmap, croppedBmp, true); //Bitmap, no transparency support
+
+                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
+                Clipboard.SetDataObject(data, true);
+            }
+        }
+
+        public void PasteFromClipboard()
+        {
+            DataObject dao = (DataObject) Clipboard.GetDataObject();
+            WriteableBitmap finalImage = null;
+            if (dao.GetDataPresent("PNG"))
+                using (MemoryStream pngStream = dao.GetData("PNG") as MemoryStream)
+                {
+                    if (pngStream != null)
+                    {
+                        PngBitmapDecoder decoder = new PngBitmapDecoder(pngStream, BitmapCreateOptions.IgnoreImageCache,
+                            BitmapCacheOption.OnLoad);
+                        finalImage = new WriteableBitmap(decoder.Frames[0].Clone());
+                    }
+                }
+            else if (dao.GetDataPresent(DataFormats.Dib))
+                finalImage = new WriteableBitmap(Clipboard.GetImage()!);
+            else if (dao.GetDataPresent(DataFormats.Bitmap))
+                finalImage = new WriteableBitmap((dao.GetData(DataFormats.Bitmap) as BitmapSource)!);
+
+            if (finalImage != null) AddImageToLayers(finalImage);
+        }
+
+        public bool IsImageInClipboard()
+        {
+            DataObject dao = (DataObject) Clipboard.GetDataObject();
+            return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
+                   dao.GetDataPresent(DataFormats.Bitmap);
+        }
+
+        private void AddImageToLayers(WriteableBitmap image)
+        {
+            ViewModelMain.Current.BitmapManager.AddNewLayer("Image", image);
+        }
+
+        public BitmapSource BitmapSelectionToBmpSource(WriteableBitmap bitmap, Coordinates[] selection)
+        {
+            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;
+            return bitmap.Crop(offsetX, offsetY, width, height);
+        }
+    }
+}

+ 13 - 12
PixiEditor/Models/Controllers/MouseMovementController.cs

@@ -1,42 +1,42 @@
-using PixiEditor.Models.Position;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Text;
-using System.Windows.Media;
+using Accessibility;
+using PixiEditor.Models.Position;
 
 
 namespace PixiEditor.Models.Controllers
 namespace PixiEditor.Models.Controllers
 {
 {
     public class MouseMovementController
     public class MouseMovementController
     {
     {
         public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
         public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
+        public bool IsRecordingChanges { get; private set; }
+        public bool ClickedOnCanvas { get; set; }
         public event EventHandler StartedRecordingChanges;
         public event EventHandler StartedRecordingChanges;
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
         public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
         public event EventHandler StoppedRecordingChanges;
         public event EventHandler StoppedRecordingChanges;
-        public bool IsRecordingChanges { get; private set; } = false;
 
 
-        public void StartRecordingMouseMovementChanges()
+        public void StartRecordingMouseMovementChanges(bool clickedOnCanvas)
         {
         {
             if (IsRecordingChanges == false)
             if (IsRecordingChanges == false)
             {
             {
                 LastMouseMoveCoordinates.Clear();
                 LastMouseMoveCoordinates.Clear();
                 IsRecordingChanges = true;
                 IsRecordingChanges = true;
+                ClickedOnCanvas = clickedOnCanvas;
                 StartedRecordingChanges?.Invoke(this, EventArgs.Empty);
                 StartedRecordingChanges?.Invoke(this, EventArgs.Empty);
             }
             }
         }
         }
+
         public void RecordMouseMovementChange(Coordinates mouseCoordinates)
         public void RecordMouseMovementChange(Coordinates mouseCoordinates)
         {
         {
-            if (IsRecordingChanges == true)
-            {
-                if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[LastMouseMoveCoordinates.Count - 1])
+            if (IsRecordingChanges)
+                if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[^1])
                 {
                 {
                     LastMouseMoveCoordinates.Add(mouseCoordinates);
                     LastMouseMoveCoordinates.Add(mouseCoordinates);
                     MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
                     MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
                 }
                 }
-            }
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Plain mose move, does not affect mouse drag recordings
+        ///     Plain mouse move, does not affect mouse drag recordings
         /// </summary>
         /// </summary>
         /// <param name="mouseCoordinates"></param>
         /// <param name="mouseCoordinates"></param>
         public void MouseMoved(Coordinates mouseCoordinates)
         public void MouseMoved(Coordinates mouseCoordinates)
@@ -49,6 +49,7 @@ namespace PixiEditor.Models.Controllers
             if (IsRecordingChanges)
             if (IsRecordingChanges)
             {
             {
                 IsRecordingChanges = false;
                 IsRecordingChanges = false;
+                ClickedOnCanvas = false;
                 StoppedRecordingChanges?.Invoke(this, EventArgs.Empty);
                 StoppedRecordingChanges?.Invoke(this, EventArgs.Empty);
             }
             }
         }
         }
@@ -63,4 +64,4 @@ public class MouseMovementEventArgs : EventArgs
     {
     {
         NewPosition = mousePosition;
         NewPosition = mousePosition;
     }
     }
-}
+}

+ 54 - 36
PixiEditor/Models/Controllers/PixelChangesController.cs

@@ -1,64 +1,82 @@
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Windows.Media;
 using System.Windows.Media;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
 
 
 namespace PixiEditor.Models.Controllers
 namespace PixiEditor.Models.Controllers
 {
 {
     public class PixelChangesController
     public class PixelChangesController
     {
     {
-        LayerChanges LastChanges { get; set; }
-        LayerChanges LastOldValues { get; set; }
+        private Dictionary<int, LayerChange> LastChanges { get; set; }
+        private Dictionary<int, LayerChange> LastOldValues { get; set; }
 
 
-        public void AddChanges(LayerChanges changes, LayerChanges oldValues)
+        public void AddChanges(LayerChange changes, LayerChange oldValues)
         {
         {
-            if(LastChanges == null)
+            if (changes.PixelChanges.ChangedPixels.Count > 0)
             {
             {
-                LastChanges = changes;
-                LastOldValues = oldValues;
-                return;
-            }
-
-            foreach (var change in changes.PixelChanges.ChangedPixels)
-            {
-                if (LastChanges.PixelChanges.ChangedPixels.ContainsKey(change.Key))
+                if (LastChanges == null)
                 {
                 {
-                    continue;
+                    LastChanges = new Dictionary<int, LayerChange> {{changes.LayerIndex, changes}};
+                    LastOldValues = new Dictionary<int, LayerChange> {{oldValues.LayerIndex, oldValues}};
+                }
+                else if (LastChanges.ContainsKey(changes.LayerIndex))
+                {
+                    AddToExistingLayerChange(changes, oldValues);
                 }
                 }
                 else
                 else
                 {
                 {
-                    LastChanges.PixelChanges.ChangedPixels.Add(change.Key, change.Value);
+                    AddNewLayerChange(changes, oldValues);
                 }
                 }
             }
             }
+        }
+
+        private void AddNewLayerChange(LayerChange changes, LayerChange oldValues)
+        {
+            LastChanges[changes.LayerIndex] = changes;
+            LastOldValues[changes.LayerIndex] = oldValues;
+        }
+
+        private void AddToExistingLayerChange(LayerChange layerChange, LayerChange oldValues)
+        {
+            foreach (var change in layerChange.PixelChanges.ChangedPixels)
+                if (LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
+                    continue;
+                else
+                    LastChanges[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
 
 
             foreach (var change in oldValues.PixelChanges.ChangedPixels)
             foreach (var change in oldValues.PixelChanges.ChangedPixels)
-            {
-                if (LastOldValues.PixelChanges.ChangedPixels.ContainsKey(change.Key))
-                {
+                if (LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.ContainsKey(change.Key))
                     continue;
                     continue;
-                }
                 else
                 else
-                {
-                    LastOldValues.PixelChanges.ChangedPixels.Add(change.Key, change.Value);
-                }
-            }
+                    LastOldValues[layerChange.LayerIndex].PixelChanges.ChangedPixels.Add(change.Key, change.Value);
         }
         }
 
 
-        public Tuple<LayerChanges, LayerChanges> PopChanges()
+        public Tuple<LayerChange, LayerChange>[] PopChanges()
         {
         {
-            Dictionary<Coordinates, Color> pixelChanges = LastChanges.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
-            Dictionary<Coordinates, Color> oldValues = LastOldValues.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
-            
-            var tmp = new LayerChanges(new BitmapPixelChanges(pixelChanges), LastChanges.LayerIndex);
-            var oldValuesTmp = new LayerChanges(new BitmapPixelChanges(oldValues), LastOldValues.LayerIndex);
-            
-            Tuple<LayerChanges, LayerChanges> outputChanges = new Tuple<LayerChanges, LayerChanges>(tmp, oldValuesTmp);
+            //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 (var change in LastChanges)
+            {
+                Dictionary<Coordinates, Color> pixelChanges =
+                    change.Value.PixelChanges.ChangedPixels.ToDictionary(entry => entry.Key, entry => entry.Value);
+                Dictionary<Coordinates, Color> oldValues = LastOldValues[change.Key].PixelChanges.ChangedPixels
+                    .ToDictionary(entry => entry.Key, entry => entry.Value);
+
+                var tmp = new LayerChange(new BitmapPixelChanges(pixelChanges), change.Key);
+                var oldValuesTmp = new LayerChange(new BitmapPixelChanges(oldValues), change.Key);
+
+                result[i] = new Tuple<LayerChange, LayerChange>(tmp, oldValuesTmp);
+                i++;
+            }
+
             LastChanges = null;
             LastChanges = null;
             LastOldValues = null;
             LastOldValues = null;
-            return outputChanges;
+            return result;
         }
         }
     }
     }
-}
+}

+ 20 - 0
PixiEditor/Models/Controllers/ReadonlyToolUtility.cs

@@ -0,0 +1,20 @@
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class ReadonlyToolUtility
+    {
+        public BitmapManager Manager { get; set; }
+
+        public ReadonlyToolUtility(BitmapManager manager)
+        {
+            Manager = manager;
+        }
+
+        public void ExecuteTool(Coordinates[] mouseMove, ReadonlyTool tool)
+        {
+            tool.Use(mouseMove);
+        }
+    }
+}

+ 5 - 7
PixiEditor/Models/Controllers/Shortcuts/Shortcut.cs

@@ -1,6 +1,6 @@
 using System.Windows.Input;
 using System.Windows.Input;
 
 
-namespace PixiEditor.Models.Controllers
+namespace PixiEditor.Models.Controllers.Shortcuts
 {
 {
     public class Shortcut
     public class Shortcut
     {
     {
@@ -9,7 +9,8 @@ namespace PixiEditor.Models.Controllers
         public ICommand Command { get; set; }
         public ICommand Command { get; set; }
         public object CommandParameter { get; set; }
         public object CommandParameter { get; set; }
 
 
-        public Shortcut(Key shortcutKey, ICommand command, object commandParameter = null, ModifierKeys modifier = ModifierKeys.None)
+        public Shortcut(Key shortcutKey, ICommand command, object commandParameter = null,
+            ModifierKeys modifier = ModifierKeys.None)
         {
         {
             ShortcutKey = shortcutKey;
             ShortcutKey = shortcutKey;
             Modifier = modifier;
             Modifier = modifier;
@@ -19,10 +20,7 @@ namespace PixiEditor.Models.Controllers
 
 
         public void Execute()
         public void Execute()
         {
         {
-            if(Command.CanExecute(CommandParameter))
-            {
-                Command.Execute(CommandParameter);
-            }
+            if (Command.CanExecute(CommandParameter)) Command.Execute(CommandParameter);
         }
         }
     }
     }
-}
+}

+ 6 - 10
PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

@@ -1,23 +1,21 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
-namespace PixiEditor.Models.Controllers
+namespace PixiEditor.Models.Controllers.Shortcuts
 {
 {
     public class ShortcutController
     public class ShortcutController
     {
     {
         public static bool BlockShortcutExecution { get; set; }
         public static bool BlockShortcutExecution { get; set; }
 
 
-        public List<Shortcut> Shortcuts { get; set; }       
+        public List<Shortcut> Shortcuts { get; set; }
 
 
         public ShortcutController()
         public ShortcutController()
         {
         {
             Shortcuts = new List<Shortcut>();
             Shortcuts = new List<Shortcut>();
-            
         }
         }
 
 
-        public void KeyPressed(Key key)
+        public void KeyPressed(Key key, ModifierKeys modifiers)
         {
         {
             if (!BlockShortcutExecution)
             if (!BlockShortcutExecution)
             {
             {
@@ -25,14 +23,12 @@ namespace PixiEditor.Models.Controllers
                 if (shortcuts.Length < 1) return;
                 if (shortcuts.Length < 1) return;
                 shortcuts = shortcuts.OrderByDescending(x => x.Modifier).ToArray();
                 shortcuts = shortcuts.OrderByDescending(x => x.Modifier).ToArray();
                 for (int i = 0; i < shortcuts.Length; i++)
                 for (int i = 0; i < shortcuts.Length; i++)
-                {
-                    if (Keyboard.Modifiers.HasFlag(shortcuts[i].Modifier))
+                    if (modifiers.HasFlag(shortcuts[i].Modifier))
                     {
                     {
                         shortcuts[i].Execute();
                         shortcuts[i].Execute();
                         break;
                         break;
                     }
                     }
-                }
             }
             }
         }
         }
     }
     }
-}
+}

+ 43 - 51
PixiEditor/Models/Controllers/UndoManager.cs

@@ -1,93 +1,85 @@
-using PixiEditor.Models.DataHolders;
-using System.Collections.Generic;
-using System.Diagnostics;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
+using PixiEditor.Models.DataHolders;
 
 
 namespace PixiEditor.Models.Controllers
 namespace PixiEditor.Models.Controllers
 {
 {
     public static class UndoManager
     public static class UndoManager
     {
     {
+        private static bool _lastChangeWasUndo;
         public static StackEx<Change> UndoStack { get; set; } = new StackEx<Change>();
         public static StackEx<Change> UndoStack { get; set; } = new StackEx<Change>();
         public static StackEx<Change> RedoStack { get; set; } = new StackEx<Change>();
         public static StackEx<Change> RedoStack { get; set; } = new StackEx<Change>();
-        private static bool _stopRecording = false;
-        private static List<Change> _recordedChanges = new List<Change>();
-        private static bool _lastChangeWasUndo = false;
-        public static bool CanUndo
-        {
-            get
-            {
-                return UndoStack.Count > 0;
-            }
-        }
-        public static bool CanRedo
-        {
-            get
-            {
-                return RedoStack.Count > 0;
-            }
-        }
+
+        public static bool CanUndo => UndoStack.Count > 0;
+
+        public static bool CanRedo => RedoStack.Count > 0;
 
 
         public static object MainRoot { get; set; }
         public static object MainRoot { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Sets object(root) in which undo properties are stored.
+        ///     Sets object(root) in which undo properties are stored.
         /// </summary>
         /// </summary>
         /// <param name="root">Parent object.</param>
         /// <param name="root">Parent object.</param>
         public static void SetMainRoot(object root)
         public static void SetMainRoot(object root)
         {
         {
             MainRoot = root;
             MainRoot = root;
         }
         }
-      
-
-        public static void AddUndoChange(Change change)
-        {
-            if (_lastChangeWasUndo == false && RedoStack.Count > 0) //Cleares RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
-            {
-                RedoStack.Clear();
-            }
-            _lastChangeWasUndo = false;
-            UndoStack.Push(change);
-        }
 
 
         /// <summary>
         /// <summary>
-        /// Adds property change to UndoStack
+        ///     Adds property change to UndoStack
         /// </summary>
         /// </summary>
-        /// <param name="property">Changed property name.</param>
-        /// <param name="oldValue">Old value of property.</param>
-        /// <param name="newValue">New value of property.</param>
-        /// <param name="undoDescription">Description of change.</param>
-        public static void AddUndoChange(string property, object oldValue, object newValue, string undoDescription = "")
+        /// <param name="change"></param>
+        public static void AddUndoChange(Change change)
         {
         {
-            if (_lastChangeWasUndo == false && RedoStack.Count > 0) //Cleares RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
-            {
+            if (_lastChangeWasUndo == false && RedoStack.Count > 0
+            ) //Clears RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
                 RedoStack.Clear();
                 RedoStack.Clear();
-            }
             _lastChangeWasUndo = false;
             _lastChangeWasUndo = false;
-            UndoStack.Push(new Change(property, oldValue, newValue, undoDescription));
+            change.Root ??= MainRoot;
+            UndoStack.Push(change);
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Sets top property in UndoStack to Old Value
+        ///     Sets top property in UndoStack to Old Value
         /// </summary>
         /// </summary>
         public static void Undo()
         public static void Undo()
         {
         {
             _lastChangeWasUndo = true;
             _lastChangeWasUndo = true;
-            PropertyInfo propInfo = MainRoot.GetType().GetProperty(UndoStack.Peek().Property);
-            propInfo.SetValue(MainRoot, UndoStack.Peek().OldValue);
-            RedoStack.Push(UndoStack.Pop());
+            Change change = UndoStack.Pop();
+            if (change.ReverseProcess == null)
+                SetPropertyValue(change.Root, change.Property, change.OldValue);
+            else
+                change.ReverseProcess(change.ReverseProcessArguments);
+            RedoStack.Push(change);
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Sets top property from RedoStack to old value
+        ///     Sets top property from RedoStack to old value
         /// </summary>
         /// </summary>
         public static void Redo()
         public static void Redo()
         {
         {
             _lastChangeWasUndo = true;
             _lastChangeWasUndo = true;
-            PropertyInfo propinfo = MainRoot.GetType().GetProperty(RedoStack.Peek().Property);
-            propinfo.SetValue(MainRoot, RedoStack.Peek().NewValue);
-            UndoStack.Push(RedoStack.Pop());
+            Change change = RedoStack.Pop();
+            if (change.ReverseProcess == null)
+                SetPropertyValue(change.Root, change.Property, change.NewValue);
+            else
+                change.Process(change.ProcessArguments);
+            UndoStack.Push(change);
+        }
+
+
+        private static void SetPropertyValue(object target, string propName, object value)
+        {
+            string[] bits = propName.Split('.');
+            for (int i = 0; i < bits.Length - 1; i++)
+            {
+                PropertyInfo propertyToGet = target.GetType().GetProperty(bits[i]);
+                target = propertyToGet.GetValue(target, null);
+            }
 
 
+            PropertyInfo propertyToSet = target.GetType().GetProperty(bits.Last());
+            propertyToSet.SetValue(target, value, null);
         }
         }
     }
     }
-}
+}

+ 68 - 0
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using PixiEditor.Exceptions;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.Tools
+{
+    public struct BitmapPixelChanges
+    {
+
+        public bool WasBuiltAsSingleColored { get; private set; }
+
+        public static BitmapPixelChanges Empty => new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
+        public Dictionary<Coordinates, Color> ChangedPixels { get; set; }
+
+        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
+        {
+            ChangedPixels = changedPixels;
+            WasBuiltAsSingleColored = false;
+        }
+
+        /// <summary>
+        ///     Builds BitmapPixelChanges with only one color for specified coordinates
+        /// </summary>
+        /// <param name="coordinates"></param>
+        /// <param name="color"></param>
+        /// <returns>Single-colored BitmapPixelChanges</returns>
+        public static BitmapPixelChanges FromSingleColoredArray(Coordinates[] coordinates, Color color)
+        {
+            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+            for (int i = 0; i < coordinates.Length; i++) dict.Add(coordinates[i], color);
+            return new BitmapPixelChanges(dict) {WasBuiltAsSingleColored = true};
+        }
+
+        /// <summary>
+        ///     Combines pixel changes array with overriding values.
+        /// </summary>
+        /// <param name="changes">BitmapPixelChanges to combine</param>
+        /// <returns>Combined BitmapPixelChanges</returns>
+        public static BitmapPixelChanges CombineOverride(BitmapPixelChanges[] changes)
+        {
+            if (changes == null || changes.Length == 0) throw new ArgumentException();
+            BitmapPixelChanges output = Empty;
+
+            for (int i = 0; i < changes.Length; i++) output.ChangedPixels.AddRangeOverride(changes[i].ChangedPixels);
+            return output;
+        }
+
+        /// <summary>
+        ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors
+        /// </summary>
+        /// <param name="coordinates"></param>
+        /// <param name="color"></param>
+        /// <returns></returns>
+        public static BitmapPixelChanges FromArrays(IEnumerable<Coordinates> coordinates, IEnumerable<Color> color)
+        {
+            var coordinateArray = coordinates.ToArray();
+            var colorArray = color.ToArray();
+            if (coordinateArray.Length != colorArray.Length) throw new ArrayLengthMismatchException();
+            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
+            for (int i = 0; i < coordinateArray.Length; i++) dict.Add(coordinateArray[i], colorArray[i]);
+            return new BitmapPixelChanges(dict);
+        }
+    }
+}

+ 27 - 9
PixiEditor/Models/DataHolders/Change.cs

@@ -1,9 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
 
 
 namespace PixiEditor.Models.DataHolders
 namespace PixiEditor.Models.DataHolders
 {
 {
@@ -18,18 +13,41 @@ namespace PixiEditor.Models.DataHolders
 
 
         public string Property { get; set; }
         public string Property { get; set; }
 
 
-        public Change(string property, object oldValue, object newValue, string description = "")
+        public Action<object[]> ReverseProcess { get; set; }
+        public Action<object[]> Process { get; set; }
+        public object[] ProcessArguments;
+        public object[] ReverseProcessArguments;
+        public object Root { get; set; }
+
+
+        public Change(string property, object oldValue, object newValue, string description = "", object root = null)
         {
         {
             Property = property;
             Property = property;
             OldValue = oldValue;
             OldValue = oldValue;
             Description = description;
             Description = description;
             NewValue = newValue;
             NewValue = newValue;
+            Root = root;
         }
         }
 
 
-        public Change()
+        public Change(string property, Action<object[]> reverseProcess, object[] reverseArguments,
+            object newValue, string description = "", object root = null)
         {
         {
-
+            Property = property;
+            ReverseProcess = reverseProcess;
+            ReverseProcessArguments = reverseArguments;
+            NewValue = newValue;
+            Description = description;
+            Root = root;
         }
         }
 
 
+        public Change(Action<object[]> reverseProcess, object[] reverseArguments,
+            Action<object[]> process, object[] processArguments, string description = "")
+        {
+            ReverseProcess = reverseProcess;
+            ReverseProcessArguments = reverseArguments;
+            Process = process;
+            ProcessArguments = processArguments;
+            Description = description;
+        }
     }
     }
-}
+}

+ 303 - 0
PixiEditor/Models/DataHolders/Document.cs

@@ -0,0 +1,303 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.Tools;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class Document : NotifyableObject
+    {
+        public int Width
+        {
+            get => _width;
+            set
+            {
+                _width = value;
+                RaisePropertyChanged("Width");
+            }
+        }
+
+        public int Height
+        {
+            get => _height;
+            set
+            {
+                _height = value;
+                RaisePropertyChanged("Height");
+            }
+        }
+
+        public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
+
+        public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
+
+        public int ActiveLayerIndex
+        {
+            get => _activeLayerIndex;
+            set
+            {
+                _activeLayerIndex = value;
+                RaisePropertyChanged("ActiveLayerIndex");
+                RaisePropertyChanged("ActiveLayer");
+            }
+        }
+
+        public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
+        private int _activeLayerIndex;
+        private int _height;
+        private int _width;
+
+        public Document(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+
+        public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
+
+        /// <summary>
+        ///     Resizes canvas to specified width and height to selected anchor
+        /// </summary>
+        /// <param name="width">New width of canvas</param>
+        /// <param name="height">New height of canvas</param>
+        /// <param name="anchor">
+        ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
+        ///     vertical.
+        /// </param>
+        public void ResizeCanvas(int width, int height, AnchorPoint anchor)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            int offsetX = GetOffsetXForAnchor(Width, width, anchor);
+            int offsetY = GetOffsetYForAnchor(Height, height, anchor);
+
+            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
+            Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
+                .ToArray();
+
+                object[] processArgs = {newOffsets, width, height};
+            object[] reverseProcessArgs = { oldOffsets, Width, Height};
+
+            ResizeCanvas(newOffsets, width, height);
+            UndoManager.AddUndoChange(new Change(ResizeCanvasProcess,
+                reverseProcessArgs, ResizeCanvasProcess, processArgs, "Resize canvas"));
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
+        }
+
+        private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
+        {
+            if (anchor.HasFlag(AnchorPoint.Center))
+                return Math.Abs(destWidth / 2 - srcWidth / 2);
+            if (anchor.HasFlag(AnchorPoint.Right)) return Math.Abs(destWidth - srcWidth);
+            return 0;
+        }
+
+        private int GetOffsetYForAnchor(int srcHeight, int destHeight, AnchorPoint anchor)
+        {
+            if (anchor.HasFlag(AnchorPoint.Middle))
+                return Math.Abs(destHeight / 2 - srcHeight / 2);
+            if (anchor.HasFlag(AnchorPoint.Bottom)) return Math.Abs(destHeight - srcHeight);
+            return 0;
+        }
+
+        /// <summary>
+        ///     Resizes all document layers using NearestNeighbor interpolation.
+        /// </summary>
+        /// <param name="newWidth">New document width</param>
+        /// <param name="newHeight">New document height</param>
+        public void Resize(int newWidth, int newHeight)
+        {
+            object[] reverseArgs = {Width, Height};
+            object[] args = {newWidth, newHeight};
+            ResizeDocument(args);
+            UndoManager.AddUndoChange(new Change( ResizeDocument, reverseArgs,
+                ResizeDocument, args, "Resize document"));
+        }
+
+        private void ResizeDocument(object[] arguments)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            int newWidth = (int) arguments[0];
+            int newHeight = (int) arguments[1];
+
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                float widthRatio = (float)newWidth / Width;
+                float heightRatio = (float)newHeight / Height;
+                int layerWidth = (int)(Layers[i].Width * widthRatio);
+                int layerHeight = (int)(Layers[i].Height * heightRatio);
+
+                Layers[i].Resize(layerWidth, layerHeight, newWidth, newHeight);
+                Layers[i].Offset = new Thickness(Layers[i].OffsetX * widthRatio, Layers[i].OffsetY * heightRatio, 0, 0);
+            }
+
+            Height = newHeight;
+            Width = newWidth;
+            DocumentSizeChanged?.Invoke(this,
+                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, newWidth, newHeight));
+        }
+
+        private void ResizeCanvasProcess(object[] arguments)
+        {
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            Thickness[] offset = (Thickness[])arguments[0];
+            int width = (int) arguments[1];
+            int height = (int) arguments[2];
+            ResizeCanvas(offset, width, height);
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
+        }
+
+        /// <summary>
+        ///     Resizes canvas
+        /// </summary>
+        /// <param name="offset">Offset of content in new canvas. It will move layer to that offset</param>
+        /// <param name="newWidth">New canvas size.</param>
+        /// <param name="newHeight">New canvas height.</param>
+        private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
+        {
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                Layers[i].Offset = offset[i];
+            }
+
+            Width = newWidth;
+            Height = newHeight;
+        }
+
+        /// <summary>
+        ///     Resizes canvas, so it fits exactly the size of drawn content, without any transparent pixels outside.
+        /// </summary>
+        public void ClipCanvas()
+        {
+            DoubleCords points = GetEdgePoints();
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+                return;
+
+            int width = biggestX - smallestX;
+            int height = biggestY - smallestY;
+            var moveVector = new Coordinates(-smallestX, -smallestY);
+
+            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
+            int oldWidth = Width;
+            int oldHeight = Height;
+
+            MoveOffsets(moveVector);
+            Width = width;
+            Height = height;
+
+            object[] reverseArguments = { oldOffsets, oldWidth, oldHeight };
+            object[] processArguments = { Layers.Select(x => x.Offset).ToArray(), width, height};
+
+            UndoManager.AddUndoChange(new Change(ResizeCanvasProcess, reverseArguments, 
+                ResizeCanvasProcess, processArguments, "Clip canvas"));
+        }
+
+        private DoubleCords GetEdgePoints()
+        {
+            var firstLayer = Layers[0];
+            int smallestX = firstLayer.OffsetX;
+            int smallestY = firstLayer.OffsetY;
+            int biggestX = smallestX + firstLayer.Width;
+            int biggestY = smallestY + firstLayer.Height;
+
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                Layers[i].ClipCanvas();
+                if (Layers[i].OffsetX < smallestX)
+                    smallestX = Layers[i].OffsetX;
+                if (Layers[i].OffsetX + Layers[i].Width > biggestX)
+                {
+                    biggestX = Layers[i].OffsetX + Layers[i].Width;
+                }
+
+                if (Layers[i].OffsetY < smallestY)
+                    smallestY = Layers[i].OffsetY;
+                if (Layers[i].OffsetY + Layers[i].Height > biggestY)
+                {
+                    biggestY = Layers[i].OffsetY + Layers[i].Height;
+                }
+            }
+
+            return new DoubleCords(new Coordinates(smallestX, smallestY), 
+                new Coordinates(biggestX, biggestY));
+
+        }
+
+        /// <summary>
+        ///     Moves offsets of layers by specified vector.
+        /// </summary>
+        /// <param name="moveVector"></param>
+        private void MoveOffsets(Coordinates moveVector)
+        {
+            for (int i = 0; i < Layers.Count; i++)
+            {
+                var offset = Layers[i].Offset;
+                Layers[i].Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0,0 );
+            }
+        }
+
+        private void MoveOffsetsProcess(object[] arguments)
+        {
+            Coordinates vector = (Coordinates) arguments[0];
+            MoveOffsets(vector);
+        }
+
+        public void CenterContent()
+        {
+            DoubleCords points = GetEdgePoints();
+
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+                return;
+
+            var contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
+            var documentCenter = CoordinatesCalculator.GetCenterPoint(new Coordinates(0, 0),
+                new Coordinates(Width - 1, Height - 1));
+            Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
+
+            MoveOffsets(moveVector);
+            UndoManager.AddUndoChange(new Change(MoveOffsetsProcess, new object[]{ new Coordinates(-moveVector.X, -moveVector.Y) }, MoveOffsetsProcess, 
+                new object[]{moveVector}, "Center content"));
+        }
+    }
+
+    public class DocumentSizeChangedEventArgs
+    {
+        public int OldWidth { get; set; }
+        public int OldHeight { get; set; }
+        public int NewWidth { get; set; }
+        public int NewHeight { get; set; }
+
+        public DocumentSizeChangedEventArgs(int oldWidth, int oldHeight, int newWidth, int newHeight)
+        {
+            OldWidth = oldWidth;
+            OldHeight = oldHeight;
+            NewWidth = newWidth;
+            NewHeight = newHeight;
+        }
+    }
+}

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

@@ -0,0 +1,24 @@
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Tools;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class LayerChange
+    {
+        public BitmapPixelChanges PixelChanges { get; set; }
+        public int LayerIndex { get; set; }
+
+        public LayerChange(BitmapPixelChanges pixelChanges, int layerIndex)
+        {
+            PixelChanges = pixelChanges;
+            LayerIndex = layerIndex;
+        }
+
+        public LayerChange(BitmapPixelChanges pixelChanges, Layer layer)
+        {
+            PixelChanges = pixelChanges;
+            LayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(layer);
+        }
+    }
+}

+ 0 - 19
PixiEditor/Models/DataHolders/LayerChanges.cs

@@ -1,19 +0,0 @@
-using PixiEditor.Models.Tools;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public class LayerChanges
-    {
-        public BitmapPixelChanges PixelChanges { get; set; }
-        public int LayerIndex { get; set; }
-
-        public LayerChanges(BitmapPixelChanges pixelChanges, int layerIndex)
-        {
-            PixelChanges = pixelChanges;
-            LayerIndex = layerIndex;
-        }
-    }
-}

+ 80 - 0
PixiEditor/Models/DataHolders/NotifyableColor.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Windows.Media;
+using PixiEditor.Helpers;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class NotifyableColor : NotifyableObject
+    {
+        public byte A
+        {
+            get => _a;
+            set
+            {
+                _a = value;
+                ColorChanged?.Invoke(this, EventArgs.Empty);
+                RaisePropertyChanged("A");
+            }
+        }
+
+        public byte R
+        {
+            get => _r;
+            set
+            {
+                _r = value;
+                ColorChanged?.Invoke(this, EventArgs.Empty);
+                RaisePropertyChanged("R");
+            }
+        }
+
+        public byte G
+        {
+            get => _g;
+            set
+            {
+                _g = value;
+                ColorChanged?.Invoke(this, EventArgs.Empty);
+                RaisePropertyChanged("G");
+            }
+        }
+
+        public byte B
+        {
+            get => _b;
+            set
+            {
+                _b = value;
+                ColorChanged?.Invoke(this, EventArgs.Empty);
+                RaisePropertyChanged("B");
+            }
+        }
+
+        private byte _a;
+
+        private byte _b;
+
+
+        private byte _g;
+
+        private byte _r;
+
+        public NotifyableColor(Color color)
+        {
+            A = color.A;
+            R = color.R;
+            G = color.G;
+            B = color.B;
+        }
+
+        public event EventHandler ColorChanged;
+
+        public void SetArgb(byte a, byte r, byte g, byte b)
+        {
+            A = a;
+            R = r;
+            G = g;
+            B = b;
+        }
+    }
+}

+ 64 - 0
PixiEditor/Models/DataHolders/Selection.cs

@@ -0,0 +1,64 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Media;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class Selection : NotifyableObject
+    {
+        public ObservableCollection<Coordinates> SelectedPoints { get; private set; }
+
+        public Layer SelectionLayer
+        {
+            get => _selectionLayer;
+            set
+            {
+                _selectionLayer = value;
+                RaisePropertyChanged("SelectionLayer");
+            }
+        }
+
+        private readonly Color _selectionBlue;
+        private Layer _selectionLayer;
+
+        public Selection(Coordinates[] selectedPoints)
+        {
+            SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
+            SelectionLayer = new Layer("_selectionLayer");
+            _selectionBlue = Color.FromArgb(127, 142, 202, 255);
+        }
+
+        public void SetSelection(Coordinates[] selection, SelectionType mode)
+        {
+            Color selectionColor = _selectionBlue;
+            switch (mode)
+            {
+                case SelectionType.New:
+                    SelectedPoints = new ObservableCollection<Coordinates>(selection);
+                    SelectionLayer.Clear();
+                    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;
+                    break;
+            }
+
+            SelectionLayer.ApplyPixels(BitmapPixelChanges.FromSingleColoredArray(selection, selectionColor));
+        }
+
+        public void Clear()
+        {
+            SelectionLayer.Clear();
+            SelectedPoints.Clear();
+        }
+    }
+}

+ 58 - 0
PixiEditor/Models/DataHolders/SerializableDocument.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using PixiEditor.Models.Images;
+using PixiEditor.Models.Layers;
+
+namespace PixiEditor.Models.DataHolders
+{
+    [Serializable]
+    public class SerializableDocument
+    {
+        public int Width { get; set; }
+        public int Height { get; set; }
+        public SerializableLayer[] Layers { get; set; }
+        public Tuple<byte, byte, byte, byte>[] Swatches { get; set; }
+
+        public SerializableDocument(Document document)
+        {
+            Width = document.Width;
+            Height = document.Height;
+            Layers = document.Layers.Select(x => new SerializableLayer(x)).ToArray();
+            Swatches = document.Swatches.Select(x => new Tuple<byte, byte, byte, byte>(x.A, x.R, x.G, x.B)).ToArray();
+        }
+
+        public Document ToDocument()
+        {
+            Document document = new Document(Width, Height)
+            {
+                Layers = ToLayers(),
+                Swatches = new ObservableCollection<Color>(Swatches.Select(x =>
+                    Color.FromArgb(x.Item1, x.Item2, x.Item3, x.Item4)))
+            };
+            return document;
+        }
+
+        private ObservableCollection<Layer> ToLayers()
+        {
+            ObservableCollection<Layer> layers = new ObservableCollection<Layer>();
+            for (int i = 0; i < Layers.Length; i++)
+            {
+                SerializableLayer serLayer = Layers[i];
+                Layer layer =
+                    new Layer(BitmapUtils.BytesToWriteableBitmap(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
+                    {
+                        IsVisible = serLayer.IsVisible,
+                        Name = serLayer.Name,
+                        Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),
+                        Opacity = serLayer.Opacity
+                    };
+                layers.Add(layer);
+            }
+
+            return layers;
+        }
+    }
+}

+ 8 - 19
PixiEditor/Models/DataHolders/StackEx.cs

@@ -1,24 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Collections.Generic;
 
 
 namespace PixiEditor.Models.DataHolders
 namespace PixiEditor.Models.DataHolders
 {
 {
     public class StackEx<T>
     public class StackEx<T>
     {
     {
-        public int Count
-        {
-            get { return items.Count; }
-        }
-
-        public T First
-        {
-            get { return items[0]; }
-        }
+        public int Count => items.Count;
 
 
-        private List<T> items = new List<T>();
+        public T First => items[0];
+        private readonly List<T> items = new List<T>();
 
 
         public void Clear()
         public void Clear()
         {
         {
@@ -26,7 +15,7 @@ namespace PixiEditor.Models.DataHolders
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Returns top object without deleting it.
+        ///     Returns top object without deleting it.
         /// </summary>
         /// </summary>
         /// <returns>Returns n - 1 item from stack.</returns>
         /// <returns>Returns n - 1 item from stack.</returns>
         public T Peek()
         public T Peek()
@@ -47,8 +36,8 @@ namespace PixiEditor.Models.DataHolders
                 items.RemoveAt(items.Count - 1);
                 items.RemoveAt(items.Count - 1);
                 return temp;
                 return temp;
             }
             }
-            else
-                return default;
+
+            return default;
         }
         }
 
 
         public void PushToBottom(T item)
         public void PushToBottom(T item)
@@ -61,4 +50,4 @@ namespace PixiEditor.Models.DataHolders
             items.RemoveAt(itemAtPosition);
             items.RemoveAt(itemAtPosition);
         }
         }
     }
     }
-}
+}

+ 0 - 60
PixiEditor/Models/DataHolders/Tuple.cs

@@ -1,60 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.DataHolders
-{
-    public class Tuple<T1, T2, T3> : IEquatable<object>
-    {
-        public T1 Item1
-        {
-            get;
-            set;
-        }
-
-        public T2 Item2
-        {
-            get;
-            set;
-        }
-
-        public T3 Item3
-        {
-            get;
-            set;
-        }
-
-        public Tuple(T1 Item1, T2 Item2, T3 Item3)
-        {
-            this.Item1 = Item1;
-            this.Item2 = Item2;
-            this.Item3 = Item3;
-        }
-
-        public override bool Equals(object obj)
-        {
-            if (obj == null || obj as Tuple<T1, T2, T3> == null) //if the object is null or the cast fails
-                return false;
-            else
-            {
-                Tuple<T1, T2, T3> tuple = (Tuple<T1, T2, T3>)obj;
-                return Item1.Equals(tuple.Item1) && Item2.Equals(tuple.Item2) && Item3.Equals(tuple.Item3);
-            }
-        }
-
-        public override int GetHashCode()
-        {
-            return Item1.GetHashCode() ^ Item2.GetHashCode() ^ Item3.GetHashCode();
-        }
-
-        public static bool operator ==(Tuple<T1, T2, T3> tuple1, Tuple<T1, T2, T3> tuple2)
-        {
-            return tuple1.Equals(tuple2);
-        }
-
-        public static bool operator !=(Tuple<T1, T2, T3> tuple1, Tuple<T1, T2, T3> tuple2)
-        {
-            return !tuple1.Equals(tuple2);
-        }
-    }
-}

+ 18 - 0
PixiEditor/Models/Dialogs/ConfirmationDialog.cs

@@ -0,0 +1,18 @@
+using PixiEditor.Models.Enums;
+using PixiEditor.Views;
+
+namespace PixiEditor.Models.Dialogs
+{
+    public static class ConfirmationDialog
+    {
+        public static ConfirmationType Show(string message)
+        {
+            ConfirmationPopup popup = new ConfirmationPopup
+            {
+                Body = message
+            };
+            if ((bool) popup.ShowDialog()) return popup.Result ? ConfirmationType.Yes : ConfirmationType.No;
+            return ConfirmationType.Canceled;
+        }
+    }
+}

+ 1 - 8
PixiEditor/Models/Dialogs/CustomDialog.cs

@@ -1,11 +1,4 @@
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
@@ -13,4 +6,4 @@ namespace PixiEditor.Models.Dialogs
     {
     {
         public abstract bool ShowDialog();
         public abstract bool ShowDialog();
     }
     }
-}
+}

+ 41 - 27
PixiEditor/Models/Dialogs/ExportFileDialog.cs

@@ -1,47 +1,60 @@
-using PixiEditor.Views;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
+using PixiEditor.Views;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
     public class ExportFileDialog : CustomDialog
     public class ExportFileDialog : CustomDialog
     {
     {
-
-        private int _fileWidth;
-
         public int FileWidth
         public int FileWidth
         {
         {
-            get { return _fileWidth; }
-            set { if (_fileWidth != value) { _fileWidth = value; RaisePropertyChanged("Width"); } }
+            get => _fileWidth;
+            set
+            {
+                if (_fileWidth != value)
+                {
+                    _fileWidth = value;
+                    RaisePropertyChanged("Width");
+                }
+            }
         }
         }
 
 
-
-        private int _fileHeight;
-
         public int FileHeight
         public int FileHeight
         {
         {
-            get { return _fileHeight; }
-            set { if (_fileHeight != value) { _fileHeight = value; RaisePropertyChanged("FileHeight"); } }
+            get => _fileHeight;
+            set
+            {
+                if (_fileHeight != value)
+                {
+                    _fileHeight = value;
+                    RaisePropertyChanged("FileHeight");
+                }
+            }
         }
         }
 
 
-
-        private string _filePath;
-
         public string FilePath
         public string FilePath
         {
         {
-            get { return _filePath; }
-            set { if (_filePath != value) { _filePath = value; RaisePropertyChanged("FilePath"); } }
+            get => _filePath;
+            set
+            {
+                if (_filePath != value)
+                {
+                    _filePath = value;
+                    RaisePropertyChanged("FilePath");
+                }
+            }
         }
         }
 
 
+        private int _fileHeight;
+
+
+        private string _filePath;
+
+        private int _fileWidth;
+
         public ExportFileDialog(Size fileDimensions)
         public ExportFileDialog(Size fileDimensions)
         {
         {
-            FileHeight = (int)fileDimensions.Height;
-            FileWidth = (int)fileDimensions.Width;
+            FileHeight = (int) fileDimensions.Height;
+            FileWidth = (int) fileDimensions.Width;
         }
         }
 
 
         public override bool ShowDialog()
         public override bool ShowDialog()
@@ -58,7 +71,8 @@ namespace PixiEditor.Models.Dialogs
                 FileHeight = popup.SaveHeight;
                 FileHeight = popup.SaveHeight;
                 FilePath = popup.SavePath;
                 FilePath = popup.SavePath;
             }
             }
-            return (bool)popup.DialogResult;
+
+            return (bool) popup.DialogResult;
         }
         }
     }
     }
-}
+}

+ 38 - 23
PixiEditor/Models/Dialogs/ImportFileDialog.cs

@@ -1,44 +1,58 @@
 using PixiEditor.Views;
 using PixiEditor.Views;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
-    class ImportFileDialog : CustomDialog
+    internal class ImportFileDialog : CustomDialog
     {
     {
-        private int _fileWidth;
-
         public int FileWidth
         public int FileWidth
         {
         {
-            get { return _fileWidth; }
-            set { if (_fileWidth != value) { _fileWidth = value; RaisePropertyChanged("Width"); } }
+            get => _fileWidth;
+            set
+            {
+                if (_fileWidth != value)
+                {
+                    _fileWidth = value;
+                    RaisePropertyChanged("Width");
+                }
+            }
         }
         }
 
 
-
-        private int _fileHeight;
-
         public int FileHeight
         public int FileHeight
         {
         {
-            get { return _fileHeight; }
-            set { if (_fileHeight != value) { _fileHeight = value; RaisePropertyChanged("FileHeight"); } }
+            get => _fileHeight;
+            set
+            {
+                if (_fileHeight != value)
+                {
+                    _fileHeight = value;
+                    RaisePropertyChanged("FileHeight");
+                }
+            }
         }
         }
 
 
-
-        private string _filePath;
-
         public string FilePath
         public string FilePath
         {
         {
-            get { return _filePath; }
-            set { if (_filePath != value) { _filePath = value; RaisePropertyChanged("FilePath"); } }
+            get => _filePath;
+            set
+            {
+                if (_filePath != value)
+                {
+                    _filePath = value;
+                    RaisePropertyChanged("FilePath");
+                }
+            }
         }
         }
 
 
+        private int _fileHeight;
+
+
+        private string _filePath;
+        private int _fileWidth;
+
         public override bool ShowDialog()
         public override bool ShowDialog()
         {
         {
             ImportFilePopup popup = new ImportFilePopup();
             ImportFilePopup popup = new ImportFilePopup();
+            popup.FilePath = FilePath;
             popup.ShowDialog();
             popup.ShowDialog();
             if (popup.DialogResult == true)
             if (popup.DialogResult == true)
             {
             {
@@ -46,7 +60,8 @@ namespace PixiEditor.Models.Dialogs
                 FileWidth = popup.ImportWidth;
                 FileWidth = popup.ImportWidth;
                 FilePath = popup.FilePath;
                 FilePath = popup.FilePath;
             }
             }
-            return (bool)popup.DialogResult;
+
+            return (bool) popup.DialogResult;
         }
         }
     }
     }
-}
+}

+ 26 - 20
PixiEditor/Models/Dialogs/NewFileDialog.cs

@@ -1,41 +1,47 @@
-using PixiEditor.Views;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+using System.Windows;
+using PixiEditor.Views;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
     public class NewFileDialog : CustomDialog
     public class NewFileDialog : CustomDialog
     {
     {
-
-        private int _width;
-
         public int Width
         public int Width
         {
         {
-            get { return _width; }
-            set { if (_width != value) { _width = value; RaisePropertyChanged("Width"); } }
+            get => _width;
+            set
+            {
+                if (_width != value)
+                {
+                    _width = value;
+                    RaisePropertyChanged("Width");
+                }
+            }
         }
         }
 
 
-
-        private int _height;
-
         public int Height
         public int Height
         {
         {
-            get { return _height; }
-            set { if (_height != value) { _height = value; RaisePropertyChanged("Height"); } }
+            get => _height;
+            set
+            {
+                if (_height != value)
+                {
+                    _height = value;
+                    RaisePropertyChanged("Height");
+                }
+            }
         }
         }
 
 
+        private int _height;
+
+        private int _width;
+
         public override bool ShowDialog()
         public override bool ShowDialog()
         {
         {
             Window popup = new NewFilePopup();
             Window popup = new NewFilePopup();
             popup.ShowDialog();
             popup.ShowDialog();
             Height = (popup as NewFilePopup).FileHeight;
             Height = (popup as NewFilePopup).FileHeight;
             Width = (popup as NewFilePopup).FileWidth;
             Width = (popup as NewFilePopup).FileWidth;
-            return (bool)popup.DialogResult;
+            return (bool) popup.DialogResult;
         }
         }
     }
     }
-}
+}

+ 89 - 0
PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs

@@ -0,0 +1,89 @@
+using PixiEditor.Models.Enums;
+using PixiEditor.Views;
+
+namespace PixiEditor.Models.Dialogs
+{
+    public class ResizeDocumentDialog : CustomDialog
+    {
+        public bool OpenResizeCanvas { get; set; }
+        public AnchorPoint ResizeAnchor { get; set; }
+
+        public int Width
+        {
+            get => _width;
+            set
+            {
+                if (_width != value)
+                {
+                    _width = value;
+                    RaisePropertyChanged("Width");
+                }
+            }
+        }
+
+        public int Height
+        {
+            get => _height;
+            set
+            {
+                if (_height != value)
+                {
+                    _height = value;
+                    RaisePropertyChanged("Height");
+                }
+            }
+        }
+
+        private int _height;
+        private int _width;
+
+        public ResizeDocumentDialog(int currentWidth, int currentHeight, bool openResizeCanvas = false)
+        {
+            Width = currentWidth;
+            Height = currentHeight;
+            OpenResizeCanvas = openResizeCanvas;
+        }
+
+        public override bool ShowDialog()
+        {
+            return OpenResizeCanvas ? ShowResizeCanvasDialog() : ShowResizeDocumentCanvas();
+        }
+
+        private bool ShowResizeDocumentCanvas()
+        {
+            ResizeDocumentPopup popup = new ResizeDocumentPopup
+            {
+                NewHeight = Height,
+                NewWidth = Width
+            };
+
+            popup.ShowDialog();
+            if (popup.DialogResult == true)
+            {
+                Width = popup.NewWidth;
+                Height = popup.NewHeight;
+            }
+
+            return (bool) popup.DialogResult;
+        }
+
+        private bool ShowResizeCanvasDialog()
+        {
+            ResizeCanvasPopup popup = new ResizeCanvasPopup
+            {
+                NewHeight = Height,
+                NewWidth = Width
+            };
+
+            popup.ShowDialog();
+            if (popup.DialogResult == true)
+            {
+                Width = popup.NewWidth;
+                Height = popup.NewHeight;
+                ResizeAnchor = popup.SelectedAnchorPoint;
+            }
+
+            return (bool) popup.DialogResult;
+        }
+    }
+}

+ 15 - 0
PixiEditor/Models/Enums/AnchorPoint.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace PixiEditor.Models.Enums
+{
+    [Flags]
+    public enum AnchorPoint
+    {
+        Left = 1,
+        Center = 2,
+        Right = 4,
+        Top = 8,
+        Middle = 16,
+        Bottom = 32
+    }
+}

+ 11 - 0
PixiEditor/Models/Enums/BrightnessMode.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PixiEditor.Models.Enums
+{
+    public enum BrightnessMode
+    {
+        Default, Repeat
+    }
+}

+ 11 - 0
PixiEditor/Models/Enums/CapType.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PixiEditor.Models.Enums
+{
+    public enum CapType
+    {
+        Square, Round
+    }
+}

+ 9 - 0
PixiEditor/Models/Enums/ConfirmationType.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.Models.Enums
+{
+    public enum ConfirmationType
+    {
+        Yes,
+        No,
+        Canceled
+    }
+}

+ 3 - 9
PixiEditor/Models/Enums/FileType.cs

@@ -1,13 +1,7 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Enums
+namespace PixiEditor.Models.Enums
 {
 {
     public enum FileType
     public enum FileType
     {
     {
-        PNG = 0,
+        PNG = 0
     }
     }
-}
+}

+ 6 - 7
PixiEditor/Models/Enums/LayerAction.cs

@@ -1,11 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.Enums
+namespace PixiEditor.Models.Enums
 {
 {
     public enum LayerAction
     public enum LayerAction
     {
     {
-        Add, Remove, Move, SetActive
+        Add,
+        Remove,
+        Move,
+        SetActive
     }
     }
-}
+}

+ 9 - 0
PixiEditor/Models/Enums/SelectionType.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.Models.Enums
+{
+    public enum SelectionType
+    {
+        New,
+        Add,
+        Subtract
+    }
+}

+ 14 - 0
PixiEditor/Models/Events/DocumentChangedEventArgs.cs

@@ -0,0 +1,14 @@
+using PixiEditor.Models.DataHolders;
+
+namespace PixiEditor.Models.Events
+{
+    public class DocumentChangedEventArgs
+    {
+        public Document NewDocument { get; set; }
+
+        public DocumentChangedEventArgs(Document newDocument)
+        {
+            NewDocument = newDocument;
+        }
+    }
+}

+ 26 - 0
PixiEditor/Models/IO/BinarySerialization.cs

@@ -0,0 +1,26 @@
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace PixiEditor.Models.IO
+{
+    public static class BinarySerialization
+    {
+        public static void WriteToBinaryFile<T>(string path, T objectToWrite)
+        {
+            using (Stream stream = File.Open(path, FileMode.Create))
+            {
+                BinaryFormatter binaryFormatter = new BinaryFormatter();
+                binaryFormatter.Serialize(stream, objectToWrite);
+            }
+        }
+
+        public static T ReadFromBinaryFile<T>(string path)
+        {
+            using (Stream stream = File.Open(path, FileMode.Open))
+            {
+                BinaryFormatter formatter = new BinaryFormatter();
+                return (T) formatter.Deserialize(stream);
+            }
+        }
+    }
+}

+ 36 - 39
PixiEditor/Models/IO/Exporter.cs

@@ -1,81 +1,78 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Diagnostics;
 using System.IO;
 using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using System.Windows;
 using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
 using Microsoft.Win32;
 using Microsoft.Win32;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Dialogs;
-using PixiEditor.Models.Enums;
 
 
 namespace PixiEditor.Models.IO
 namespace PixiEditor.Models.IO
 {
 {
     public class Exporter
     public class Exporter
     {
     {
-        public static string SavePath = null;
         public static Size FileDimensions;
         public static Size FileDimensions;
+        public static string SaveDocumentPath { get; set; }
+
+        public static void SaveAsNewEditableFile(Document document, bool updateWorkspacePath = false)
+        {
+            SaveFileDialog dialog = new SaveFileDialog
+            {
+                Filter = "PixiEditor Files | *.pixi",
+                DefaultExt = "pixi"
+            };
+            if ((bool) dialog.ShowDialog()) SaveAsEditableFile(document, dialog.FileName, updateWorkspacePath);
+        }
+
+        public static void SaveAsEditableFile(Document document, string path, bool updateWorkspacePath = false)
+        {
+            BinarySerialization.WriteToBinaryFile(path, new SerializableDocument(document));
+
+            if (updateWorkspacePath)
+                SaveDocumentPath = path;
+        }
 
 
         /// <summary>
         /// <summary>
-        /// Creates ExportFileDialog to get width, height and path of file.
+        ///     Creates ExportFileDialog to get width, height and path of file.
         /// </summary>
         /// </summary>
-        /// <param name="type">Type of file to be saved in.</param>
         /// <param name="bitmap">Bitmap to be saved as file.</param>
         /// <param name="bitmap">Bitmap to be saved as file.</param>
-        public static void Export(FileType type, WriteableBitmap bitmap, Size fileDimensions)
+        /// <param name="fileDimensions">Size of file</param>
+        public static void Export(WriteableBitmap bitmap, Size fileDimensions)
         {
         {
             ExportFileDialog info = new ExportFileDialog(fileDimensions);
             ExportFileDialog info = new ExportFileDialog(fileDimensions);
             //If OK on dialog has been clicked
             //If OK on dialog has been clicked
-            if (info.ShowDialog() == true)
+            if (info.ShowDialog())
             {
             {
                 //If sizes are incorrect
                 //If sizes are incorrect
                 if (info.FileWidth < bitmap.Width || info.FileHeight < bitmap.Height)
                 if (info.FileWidth < bitmap.Width || info.FileHeight < bitmap.Height)
                 {
                 {
-                    MessageBox.Show("Incorrect height or width value", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+                    MessageBox.Show("Incorrect height or width value", "Error", MessageBoxButton.OK,
+                        MessageBoxImage.Error);
                     return;
                     return;
                 }
                 }
 
 
-                SavePath = info.FilePath;
                 FileDimensions = new Size(info.FileWidth, info.FileHeight);
                 FileDimensions = new Size(info.FileWidth, info.FileHeight);
                 SaveAsPng(info.FilePath, info.FileHeight, info.FileWidth, bitmap);
                 SaveAsPng(info.FilePath, info.FileHeight, info.FileWidth, bitmap);
             }
             }
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Saves file with info that has been recieved from ExportFileDialog before, doesn't work without before Export() usage.
-        /// </summary>
-        /// <param name="type">Type of file</param>
-        /// <param name="bitmap">Image to be saved as file.</param>
-        public static void ExportWithoutDialog(FileType type, WriteableBitmap bitmap)
-        {
-            try
-            {
-                SaveAsPng(SavePath, (int)FileDimensions.Height, (int)FileDimensions.Width, bitmap);
-            }
-            catch (Exception ex)
-            {
-                MessageBox.Show(ex.Message);
-            }
-        }
-        /// <summary>
-        /// Saves image to PNG file
+        ///     Saves image to PNG file
         /// </summary>
         /// </summary>
         /// <param name="savePath">Save file path</param>
         /// <param name="savePath">Save file path</param>
         /// <param name="exportWidth">File width</param>
         /// <param name="exportWidth">File width</param>
         /// <param name="exportHeight">File height</param>
         /// <param name="exportHeight">File height</param>
+        /// <param name="bitmap">Bitmap to save</param>
         private static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
         private static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
         {
         {
             try
             try
             {
             {
-                bitmap = bitmap.Resize(exportWidth, exportHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-                using(FileStream stream = new FileStream(savePath, FileMode.Create))
-                {
-                    PngBitmapEncoder encoder = new PngBitmapEncoder();
-                    encoder.Frames.Add(BitmapFrame.Create(bitmap));
-                    encoder.Save(stream);
+                bitmap = bitmap.Resize(exportWidth, exportHeight,
+                    WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+                using (FileStream stream = new FileStream(savePath, FileMode.Create))
+                {
+                    PngBitmapEncoder encoder = new PngBitmapEncoder();
+                    encoder.Frames.Add(BitmapFrame.Create(bitmap));
+                    encoder.Save(stream);
                 }
                 }
             }
             }
             catch (Exception err)
             catch (Exception err)
@@ -84,4 +81,4 @@ namespace PixiEditor.Models.IO
             }
             }
         }
         }
     }
     }
-}
+}

+ 10 - 24
PixiEditor/Models/IO/PixiFilesManager.cs → PixiEditor/Models/IO/FilesManager.cs

@@ -1,32 +1,21 @@
 using System;
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.IO;
 using System.IO;
-using System.Text;
 using Newtonsoft.Json;
 using Newtonsoft.Json;
 
 
 namespace PixiEditor.Models.IO
 namespace PixiEditor.Models.IO
 {
 {
-    public static class PixiFilesManager
+    public static class FilesManager
     {
     {
+        public static string TempFolderPath =>
+            Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"PixiEditor\Temp");
 
 
-        public static string TempFolderPath
-        {
-            get { return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"PixiEditor\Temp"); }
-        }
+        public static string RedoStackPath => Path.Combine(TempFolderPath, @"RedoStack");
 
 
-        public static string RedoStackPath
-        {
-            get { return Path.Combine(TempFolderPath, @"RedoStack"); }
-        }
-
-        public static string UndoStackPath
-        {
-            get { return Path.Combine(TempFolderPath, @"UndoStack"); }
-        }
+        public static string UndoStackPath => Path.Combine(TempFolderPath, @"UndoStack");
 
 
         /// <summary>
         /// <summary>
-        /// Saves object to file on disk using binary formatter
+        ///     Saves object to file on disk using binary formatter
         /// </summary>
         /// </summary>
         /// <param name="obj">Object to be saved</param>
         /// <param name="obj">Object to be saved</param>
         public static void SaveObjectToJsonFile<T>(T obj, string fileName) where T : new()
         public static void SaveObjectToJsonFile<T>(T obj, string fileName) where T : new()
@@ -48,16 +37,13 @@ namespace PixiEditor.Models.IO
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Removes all files from directory
+        ///     Removes all files from directory
         /// </summary>
         /// </summary>
         /// <param name="path"></param>
         /// <param name="path"></param>
         public static void ClearDirectory(string path)
         public static void ClearDirectory(string path)
         {
         {
             string[] filesInDirectory = Directory.GetFiles(path);
             string[] filesInDirectory = Directory.GetFiles(path);
-            for (int i = 0; i < filesInDirectory.Length; i++)
-            {
-                File.Delete(filesInDirectory[i]);
-            }
+            for (int i = 0; i < filesInDirectory.Length; i++) File.Delete(filesInDirectory[i]);
         }
         }
 
 
         private static void SaveSerializedObjectToFile(object obj, string filename)
         private static void SaveSerializedObjectToFile(object obj, string filename)
@@ -79,7 +65,7 @@ namespace PixiEditor.Models.IO
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates and cleares temp directories
+        ///     Creates and cleares temp directories
         /// </summary>
         /// </summary>
         public static void InitializeTempDirectories()
         public static void InitializeTempDirectories()
         {
         {
@@ -100,4 +86,4 @@ namespace PixiEditor.Models.IO
             ClearDirectory(UndoStackPath);
             ClearDirectory(UndoStackPath);
         }
         }
     }
     }
-}
+}

+ 19 - 12
PixiEditor/Models/IO/Importer.cs

@@ -1,18 +1,14 @@
-using PixiEditor.Helpers;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
-using System.Windows.Media;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
 
 
 namespace PixiEditor.Models.IO
 namespace PixiEditor.Models.IO
 {
 {
     public class Importer : NotifyableObject
     public class Importer : NotifyableObject
     {
     {
         /// <summary>
         /// <summary>
-        /// Imports image from path and resizes it to given dimensions
+        ///     Imports image from path and resizes it to given dimensions
         /// </summary>
         /// </summary>
         /// <param name="path">Path of image.</param>
         /// <param name="path">Path of image.</param>
         /// <param name="width">New width of image.</param>
         /// <param name="width">New width of image.</param>
@@ -24,10 +20,21 @@ namespace PixiEditor.Models.IO
             BitmapImage bitmap = new BitmapImage();
             BitmapImage bitmap = new BitmapImage();
             bitmap.BeginInit();
             bitmap.BeginInit();
             bitmap.UriSource = uri;
             bitmap.UriSource = uri;
-            bitmap.DecodePixelWidth = width;
-            bitmap.DecodePixelHeight = height;
             bitmap.EndInit();
             bitmap.EndInit();
-            return new WriteableBitmap(bitmap);
+
+            var wbmp = new WriteableBitmap(bitmap);
+            wbmp = wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            return wbmp;
+        }
+
+        public static Document ImportDocument(string path)
+        {
+            return BinarySerialization.ReadFromBinaryFile<SerializableDocument>(path).ToDocument();
+        }
+
+        public static bool IsSupportedFile(string path)
+        {
+            return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
         }
         }
     }
     }
-}
+}

+ 23 - 43
PixiEditor/Models/ImageManipulation/Morphology.cs

@@ -1,14 +1,12 @@
-using PixiEditor.Models.Position;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
 using System.Linq;
+using PixiEditor.Models.Position;
 
 
 namespace PixiEditor.Models.ImageManipulation
 namespace PixiEditor.Models.ImageManipulation
 {
 {
     public class Morphology
     public class Morphology
     {
     {
-
         public static Coordinates[] ApplyDilation(Coordinates[] points, int kernelSize, int[,] mask)
         public static Coordinates[] ApplyDilation(Coordinates[] points, int kernelSize, int[,] mask)
         {
         {
             int kernelDim = kernelSize;
             int kernelDim = kernelSize;
@@ -24,30 +22,21 @@ namespace PixiEditor.Models.ImageManipulation
             int width = byteImg.GetLength(0);
             int width = byteImg.GetLength(0);
             int height = byteImg.GetLength(1);
             int height = byteImg.GetLength(1);
             for (int y = kernelOffset; y < height - kernelOffset; y++)
             for (int y = kernelOffset; y < height - kernelOffset; y++)
+            for (int x = kernelOffset; x < width - kernelOffset; x++)
             {
             {
-                for (int x = kernelOffset; x < width - kernelOffset; x++)
-                {
-                    byte value = 0;
+                byte value = 0;
 
 
-                    //Apply dilation
-                    for (int ykernel = -kernelOffset; ykernel <= kernelOffset; ykernel++)
-                    {
-                        for (int xkernel = -kernelOffset; xkernel <= kernelOffset; xkernel++)
-                        {
-                            if (mask[xkernel + kernelOffset, ykernel + kernelOffset] == 1)
-                            {
-                                value = Math.Max(value, byteImg[x + xkernel, y + ykernel]);
-                            }
-                            else
-                            {
-                                continue;
-                            }
-                        }
-                    }
-                    //Write processed data into the second array
-                    outputArray[x, y] = value;
-                }
+                //Apply dilation
+                for (int ykernel = -kernelOffset; ykernel <= kernelOffset; ykernel++)
+                for (int xkernel = -kernelOffset; xkernel <= kernelOffset; xkernel++)
+                    if (mask[xkernel + kernelOffset, ykernel + kernelOffset] == 1)
+                        value = Math.Max(value, byteImg[x + xkernel, y + ykernel]);
+                    else
+                        continue;
+                //Write processed data into the second array
+                outputArray[x, y] = value;
             }
             }
+
             return ToCoordinates(outputArray, offset).Distinct().ToArray();
             return ToCoordinates(outputArray, offset).Distinct().ToArray();
         }
         }
 
 
@@ -57,16 +46,9 @@ namespace PixiEditor.Models.ImageManipulation
             int width = byteArray.GetLength(0);
             int width = byteArray.GetLength(0);
 
 
             for (int y = 0; y < byteArray.GetLength(1); y++)
             for (int y = 0; y < byteArray.GetLength(1); y++)
-            {
-                for (int x = 0; x < width; x++)
-                {
-                    if(byteArray[x,y] == 1)
-                    {
-                        output.Add(new Coordinates(x + offset.X, y + offset.Y));
-                    }
-
-                }
-            }
+            for (int x = 0; x < width; x++)
+                if (byteArray[x, y] == 1)
+                    output.Add(new Coordinates(x + offset.X, y + offset.Y));
             return output.ToArray();
             return output.ToArray();
         }
         }
 
 
@@ -79,12 +61,10 @@ namespace PixiEditor.Models.ImageManipulation
             //Debug.Write("----------\n");
             //Debug.Write("----------\n");
 
 
             for (int y = 0; y < dimensions.Item2 + margin; y++)
             for (int y = 0; y < dimensions.Item2 + margin; y++)
+            for (int x = 0; x < dimensions.Item1 + margin; x++)
             {
             {
-                for (int x = 0; x < dimensions.Item1 + margin; x++)
-                {
-                    Coordinates cords = new Coordinates(x + minX, y + minY);
-                    array[x + margin,y + margin] = points.Contains(cords) ? (byte)1 : (byte)0;
-                }
+                Coordinates cords = new Coordinates(x + minX, y + minY);
+                array[x + margin, y + margin] = points.Contains(cords) ? (byte) 1 : (byte) 0;
             }
             }
 
 
             //for (int y = 0; y < array.GetLength(1); y++)
             //for (int y = 0; y < array.GetLength(1); y++)
@@ -101,9 +81,9 @@ namespace PixiEditor.Models.ImageManipulation
 
 
         private static Tuple<int, int> GetDimensionsForPoints(Coordinates[] points)
         private static Tuple<int, int> GetDimensionsForPoints(Coordinates[] points)
         {
         {
-            int width = points.Max(x=> x.X) - points.Min(x => x.X);
-            int height = points.Max(x=> x.Y) - points.Min(x => x.Y);
+            int width = points.Max(x => x.X) - points.Min(x => x.X);
+            int height = points.Max(x => x.Y) - points.Min(x => x.Y);
             return new Tuple<int, int>(width + 1, height + 1);
             return new Tuple<int, int>(width + 1, height + 1);
         }
         }
     }
     }
-}
+}

+ 28 - 0
PixiEditor/Models/ImageManipulation/Transform.cs

@@ -0,0 +1,28 @@
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public static class Transform
+    {
+        /// <summary>
+        ///     Returns translation between two coordinates.
+        /// </summary>
+        /// <param name="from">Starting coordinate</param>
+        /// <param name="to">New coordinate</param>
+        /// <returns>Translation as coordinate</returns>
+        public static Coordinates GetTranslation(Coordinates from, Coordinates to)
+        {
+            int translationX = to.X - from.X;
+            int translationY = to.Y - from.Y;
+            return new Coordinates(translationX, translationY);
+        }
+
+        public static Coordinates[] Translate(Coordinates[] points, Coordinates vector)
+        {
+            Coordinates[] translatedPoints = new Coordinates[points.Length];
+            for (int i = 0; i < translatedPoints.Length; i++)
+                translatedPoints[i] = new Coordinates(points[i].X + vector.X, points[i].Y + vector.Y);
+            return translatedPoints;
+        }
+    }
+}

+ 0 - 22
PixiEditor/Models/Images/BitmapConverter.cs

@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Drawing;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-
-namespace PixiEditor.Models.Images
-{
-    public static class BitmapConverter
-    {
-        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
-        {
-            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            bitmap.FromByteArray(byteArray);
-            return bitmap;
-        }      
-    }
-}

+ 84 - 0
PixiEditor/Models/Images/BitmapUtils.cs

@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
+using Color = System.Windows.Media.Color;
+
+namespace PixiEditor.Models.Images
+{
+    public static class BitmapUtils
+    {
+        public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight,
+            byte[] byteArray)
+        {
+            WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
+            bitmap.FromByteArray(byteArray);
+            return bitmap;
+        }
+
+        public static BitmapSource BitmapToBitmapSource(Bitmap bitmap)
+        {
+            return Imaging.CreateBitmapSourceFromHBitmap(
+                bitmap.GetHbitmap(),
+                IntPtr.Zero,
+                Int32Rect.Empty,
+                BitmapSizeOptions.FromEmptyOptions());
+        }
+
+        public static WriteableBitmap CombineLayers(Layer[] layers)
+        {
+            int width = ViewModelMain.Current.BitmapManager.ActiveDocument.Width;
+            int height = ViewModelMain.Current.BitmapManager.ActiveDocument.Height;
+
+            WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
+
+            using (finalBitmap.GetBitmapContext())
+            {
+                for (int i = 0; i < layers.Length; i++)
+                for (int y = 0; y < finalBitmap.Height; y++)
+                for (int x = 0; x < finalBitmap.Width; x++)
+                {
+                    Color color = layers[i].GetPixelWithOffset(x, y);
+                    color = Color.FromArgb((byte)(color.A * layers[i].Opacity), color.R,color.G, color.B);
+                    if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0) finalBitmap.SetPixel(x, y, color);
+                }
+            }
+
+            return finalBitmap;
+        }
+
+        public static Dictionary<Layer, Color[]> GetPixelsForSelection(Layer[] layers, Coordinates[] selection)
+        {
+            Dictionary<Layer, Color[]> result = new Dictionary<Layer, Color[]>();
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Color[] pixels = new Color[selection.Length];
+
+                using (layers[i].LayerBitmap.GetBitmapContext())
+                {
+
+                    for (int j = 0; j < pixels.Length; j++)
+                    {
+                        Coordinates position = layers[i].GetRelativePosition(selection[j]);
+                        if (position.X < 0 || position.X > layers[i].Width - 1 || position.Y < 0 ||
+                            position.Y > layers[i].Height - 1)
+                            continue;
+                        pixels[j] = layers[i].GetPixel(position.X, position.Y);
+                    }
+                }
+
+
+                result[layers[i]] = pixels;
+            }
+
+            return result;
+        }
+    }
+}

+ 0 - 24
PixiEditor/Models/Images/ImageGenerator.cs

@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-using System.Windows.Media;
-
-namespace PixiEditor.Models.Images
-{
-    public static class ImageGenerator
-    {
-        public static Image GenerateForPixelArts(int width, int height)
-        {
-            Image image = new Image();
-            RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.NearestNeighbor);
-            RenderOptions.SetEdgeMode(image, EdgeMode.Aliased);
-            image.Stretch = Stretch.Uniform;
-            image.Width = width;
-            image.Height = height;
-            return image;
-        }
-    }
-}

+ 17 - 15
PixiEditor/Models/Layers/BasicLayer.cs

@@ -1,9 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Controls;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 
 
 namespace PixiEditor.Models.Layers
 namespace PixiEditor.Models.Layers
@@ -11,21 +6,28 @@ namespace PixiEditor.Models.Layers
     [Serializable]
     [Serializable]
     public class BasicLayer : NotifyableObject
     public class BasicLayer : NotifyableObject
     {
     {
-
-        private int _width;
-
         public int Width
         public int Width
         {
         {
-            get { return _width; }
-            set { _width = value; RaisePropertyChanged("Width"); }
+            get => _width;
+            set
+            {
+                _width = value;
+                RaisePropertyChanged("Width");
+            }
         }
         }
 
 
-        private int _height;
-
         public int Height
         public int Height
         {
         {
-            get { return _height; }
-            set { _height = value; RaisePropertyChanged("Height"); }
+            get => _height;
+            set
+            {
+                _height = value;
+                RaisePropertyChanged("Height");
+            }
         }
         }
+
+        private int _height;
+
+        private int _width;
     }
     }
-}
+}

+ 379 - 77
PixiEditor/Models/Layers/Layer.cs

@@ -1,59 +1,60 @@
-using PixiEditor.Models.Tools;
-using System;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
 
 
 namespace PixiEditor.Models.Layers
 namespace PixiEditor.Models.Layers
 {
 {
     public class Layer : BasicLayer
     public class Layer : BasicLayer
     {
     {
-        private WriteableBitmap _layerBitmap;
-        private string _name;
-
-        public string Name
-        {
-            get { return _name; }
-            set 
-            { 
-                _name = value;
-                RaisePropertyChanged("Name");
-            }
-        }
-
-
-        private bool _isActive = false;
-        public bool IsActive
-        {
-            get => _isActive;
-            set
-            {
-                _isActive = value;
-                RaisePropertyChanged("IsActive");
-            }
+
+        private const int SizeOfArgb = 4;
+
+        public string Name
+        {
+            get => _name;
+            set
+            {
+                _name = value;
+                RaisePropertyChanged("Name");
+            }
         }
         }
-        private bool _isVisible = true;
-        public bool IsVisible
-        {
-            get => _isVisible;
-            set
-            {
-                _isVisible = value;
-                RaisePropertyChanged("IsVisible");
-            }
-        }
-
-        private bool _isRenaming = false;
-
-        public bool IsRenaming
-        {
-            get { return _isRenaming; }
-            set
-            {
-                _isRenaming = value;
-                RaisePropertyChanged("IsRenaming");
-            }
-        }
-
-
+
+        public bool IsActive
+        {
+            get => _isActive;
+            set
+            {
+                _isActive = value;
+                RaisePropertyChanged("IsActive");
+            }
+        }
+
+        public bool IsVisible
+        {
+            get => _isVisible;
+            set
+            {
+                _isVisible = value;
+                RaisePropertyChanged("IsVisible");
+            }
+        }
+
+        public bool IsRenaming
+        {
+            get => _isRenaming;
+            set
+            {
+                _isRenaming = value;
+                RaisePropertyChanged("IsRenaming");
+            }
+        }
+
+
         public WriteableBitmap LayerBitmap
         public WriteableBitmap LayerBitmap
         {
         {
             get => _layerBitmap;
             get => _layerBitmap;
@@ -64,6 +65,57 @@ namespace PixiEditor.Models.Layers
             }
             }
         }
         }
 
 
+        private float _opacity = 1;
+
+        public float Opacity
+        {
+            get => _opacity;
+            set
+            {
+                _opacity = value;
+                RaisePropertyChanged("Opacity");
+            }
+        }
+
+        public int OffsetX => (int) Offset.Left;
+
+        public int OffsetY => (int) Offset.Top;
+
+        private Thickness _offset;
+
+        public Thickness Offset
+        {
+            get => _offset;
+            set
+            {
+                _offset = value;
+                RaisePropertyChanged("Offset");
+            }
+        }
+
+        private bool _isActive;
+
+        private bool _isRenaming;
+        private bool _isVisible = true;
+        private WriteableBitmap _layerBitmap;
+
+        private string _name;
+        private bool _clipRequested;
+
+        public int MaxWidth { get; set; } = int.MaxValue;
+        public int MaxHeight { get; set; } = int.MaxValue;
+
+        public Dictionary<Coordinates,Color> LastRelativeCoordinates;
+
+        public Layer(string name)
+        {
+            Name = name;
+            Layer layer = LayerGenerator.Generate(0, 0);
+            LayerBitmap = layer.LayerBitmap;
+            Width = 0;
+            Height = 0;
+        }
+
         public Layer(string name, int width, int height)
         public Layer(string name, int width, int height)
         {
         {
             Name = name;
             Name = name;
@@ -77,53 +129,303 @@ namespace PixiEditor.Models.Layers
         public Layer(WriteableBitmap layerBitmap)
         public Layer(WriteableBitmap layerBitmap)
         {
         {
             LayerBitmap = layerBitmap;
             LayerBitmap = layerBitmap;
-            Width = (int)layerBitmap.Width;
-            Height = (int)layerBitmap.Height;
+            Width = (int) layerBitmap.Width;
+            Height = (int) layerBitmap.Height;
         }
         }
 
 
-        public void ApplyPixels(BitmapPixelChanges pixels)
+        /// <summary>
+        /// Returns clone of layer
+        /// </summary>
+        /// <returns></returns>
+        public Layer Clone()
         {
         {
-            LayerBitmap.Lock();
+            return new Layer(LayerBitmap.Clone())
+            {
+                _isVisible = this._isVisible,
+                Name = this.Name,
+                Offset = this.Offset,
+                MaxHeight = this.MaxHeight,
+                MaxWidth = this.MaxWidth,
+                Opacity = this.Opacity
+            };
+        }
+
+        public void Resize(int width, int height, int newMaxWidth, int newMaxHeight)
+        {
+            LayerBitmap = LayerBitmap.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+            Width = width;
+            Height = height;
+            MaxWidth = newMaxWidth;
+            MaxHeight = newMaxHeight;
+        }
 
 
-            foreach (var coords in pixels.ChangedPixels)
+        /// <summary>
+        ///     Converts coordinates relative to viewport to relative to layer
+        /// </summary>
+        /// <param name="cords"></param>
+        /// <returns></returns>
+        public Coordinates GetRelativePosition(Coordinates cords)
+        {
+            return new Coordinates(cords.X - OffsetX, cords.Y - OffsetY);
+        }
+
+        /// <summary>
+        ///     Returns pixel color by x and y coordinates relative to document using (x - OffsetX) formula.
+        /// </summary>
+        /// <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)
+        {
+            Coordinates cords = GetRelativePosition(new Coordinates(x, y));
+            return GetPixel(cords.X, cords.Y);
+        }
+
+        /// <summary>
+        ///     Returns pixel color on x and y.
+        /// </summary>
+        /// <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)
+        {
+            if (x > Width - 1 || x < 0 || y > Height - 1 || y < 0)
             {
             {
-                if (coords.Key.X > Width - 1 || coords.Key.X < 0 || coords.Key.Y < 0 || coords.Key.Y > Height - 1) continue;
-                LayerBitmap.SetPixel(Math.Clamp(coords.Key.X, 0, Width - 1), Math.Clamp(coords.Key.Y, 0, Height - 1),
-                    coords.Value);
+                return Color.FromArgb(0, 0, 0, 0);
             }
             }
 
 
-            LayerBitmap.Unlock();
+            return LayerBitmap.GetPixel(x, y);
         }
         }
 
 
-        public void Clear()
-        {
-            LayerBitmap.Lock();
-            LayerBitmap.Clear();
-            LayerBitmap.Unlock();
+        /// <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 ApplyPixel(Coordinates coordinates, Color color, bool dynamicResize = true, bool applyOffset = true)
+        {
+            ApplyPixels(BitmapPixelChanges.FromSingleColoredArray(new []{ coordinates }, color), dynamicResize, applyOffset);
+        }
+
+        /// <summary>
+        ///     Applies pixels to layer
+        /// </summary>
+        /// <param name="pixels">Pixels to apply</param>
+        /// <param name="dynamicResize">Resizes bitmap to fit content</param>
+        /// <param name="applyOffset">Converts pixels coordinates to relative to bitmap</param>
+        public void ApplyPixels(BitmapPixelChanges pixels, bool dynamicResize = true, bool applyOffset = true)
+        {
+            if (pixels.ChangedPixels == null || pixels.ChangedPixels.Count == 0) return;
+            if(dynamicResize)
+                DynamicResize(pixels);
+            if(applyOffset)
+                pixels.ChangedPixels = GetRelativePosition(pixels.ChangedPixels);
+            LastRelativeCoordinates = pixels.ChangedPixels;
+
+            using (var ctx = LayerBitmap.GetBitmapContext())
+            {
+                foreach (var coords in pixels.ChangedPixels)
+                {
+                    if (OutOfBounds(coords.Key)) continue;
+                    ctx.WriteableBitmap.SetPixel(coords.Key.X, coords.Key.Y, coords.Value);
+                }
+            }
+            ClipIfNecessary();
+        }
+
+        private Dictionary<Coordinates, Color> GetRelativePosition(Dictionary<Coordinates, Color> changedPixels)
+        {
+            return changedPixels.ToDictionary(d => new Coordinates(d.Key.X - OffsetX, d.Key.Y - OffsetY),
+                d => d.Value);
+        }
+
+        public Coordinates[] ConvertToRelativeCoordinates(Coordinates[] nonRelativeCords)
+        {
+            Coordinates[] result = new Coordinates[nonRelativeCords.Length];
+            for (int i = 0; i < nonRelativeCords.Length; i++)
+            {
+                result[i] = new Coordinates(nonRelativeCords[i].X - OffsetX, nonRelativeCords[i].Y - OffsetY);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        ///     Resizes canvas to fit pixels outside current bounds. Clamped to MaxHeight and MaxWidth
+        /// </summary>
+        /// <param name="pixels"></param>
+        public void DynamicResize(BitmapPixelChanges pixels)
+        {
+            if (pixels.ChangedPixels.Count == 0) return;
+            ResetOffset(pixels);
+            var 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))
+            {
+                if (newMaxX + 1 > Width || newMaxY + 1 > Height)
+                {
+                    IncreaseSizeToBottom(newMaxX, newMaxY);
+                }
+                if (newMinX < 0 || newMinY < 0)
+                {
+                    IncreaseSizeToTop(newMinX, newMinY);
+                }
+            }
+
+            if(borderData.Item2) //if clip is requested
+            {
+                _clipRequested = true;
+            }
+        }
+
+        private Tuple<DoubleCords, bool> ExtractBorderData(BitmapPixelChanges pixels)
+        {
+            Coordinates firstCords = pixels.ChangedPixels.First().Key;
+            int minX = firstCords.X;
+            int minY = firstCords.Y;
+            int maxX = minX;
+            int maxY = minY;
+            bool clipRequested = false;
+
+            foreach (var pixel in pixels.ChangedPixels)
+            {
+                if (pixel.Key.X < minX) minX = pixel.Key.X;
+                else if (pixel.Key.X > maxX) maxX = pixel.Key.X;
+
+                if (pixel.Key.Y < minY) minY = pixel.Key.Y;
+                else if (pixel.Key.Y > maxY) maxY = pixel.Key.Y;
+
+                if (clipRequested == false && IsBorderPixel(pixel.Key) && pixel.Value.A == 0)
+                    clipRequested = true;
+
+            }
+            return new Tuple<DoubleCords, bool>(
+                new DoubleCords(new Coordinates(minX, minY), new Coordinates(maxX, maxY)), clipRequested);
+        }
+
+        private bool IsBorderPixel(Coordinates cords)
+        {
+            return cords.X - OffsetX == 0 || cords.Y - OffsetY == 0 || cords.X - OffsetX == Width - 1 ||
+                   cords.Y - OffsetY == Height - 1;
+        }
+
+        private bool OutOfBounds(Coordinates cords)
+        {
+            return cords.X < 0 || cords.X > Width - 1 || cords.Y < 0 || cords.Y > Height - 1;
+        }
+
+        private void ClipIfNecessary()
+        {
+            if (_clipRequested)
+            {
+                ClipCanvas();
+                _clipRequested = false;
+            }
+        }
+
+        public void ClipCanvas()
+        {
+            var points = GetEdgePoints();
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
+
+            if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
+                return;
+
+            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);
+        }
+
+        private void IncreaseSizeToBottom(int newMaxX, int newMaxY)
+        {
+            if (MaxWidth - OffsetX < 0 || MaxHeight - OffsetY < 0) return;
+            newMaxX = Math.Clamp(Math.Max(newMaxX + 1, Width), 0, MaxWidth - OffsetX);
+            newMaxY = Math.Clamp(Math.Max(newMaxY + 1, Height), 0, MaxHeight - OffsetY);
+
+            ResizeCanvas(0, 0, 0, 0, newMaxX, newMaxY);
+        }
+
+        private void IncreaseSizeToTop(int newMinX, int newMinY)
+        {
+            newMinX = Math.Clamp(Math.Min(newMinX, Width), -OffsetX, 0);
+            newMinY = Math.Clamp(Math.Min(newMinY, Height), -OffsetY, 0);
+
+            Offset = new Thickness(Math.Clamp(OffsetX + newMinX, 0, MaxWidth),
+                Math.Clamp(OffsetY + newMinY, 0, MaxHeight), 0, 0);
+
+            int newWidth = Math.Clamp(Width - newMinX, 0, MaxWidth);
+            int newHeight = Math.Clamp(Height - newMinY, 0, MaxHeight);
+
+            int offsetX = Math.Abs(newWidth - Width);
+            int offsetY = Math.Abs(newHeight - Height);
+
+            ResizeCanvas(offsetX, offsetY, 0, 0, newWidth, newHeight);
+        }
+
+        private DoubleCords GetEdgePoints()
+        {
+            Coordinates smallestPixel = CoordinatesCalculator.FindMinEdgeNonTransparentPixel(LayerBitmap);
+            Coordinates biggestPixel = CoordinatesCalculator.FindMostEdgeNonTransparentPixel(LayerBitmap);
+
+            return new DoubleCords(smallestPixel, biggestPixel);
+        }
+
+        private void ResetOffset(BitmapPixelChanges pixels)
+        {
+            if (Width == 0 || Height == 0)
+            {
+                int offsetX = pixels.ChangedPixels.Min(x => x.Key.X);
+                int offsetY = pixels.ChangedPixels.Min(x => x.Key.Y);
+                Offset = new Thickness(offsetX, offsetY, 0,0);
+            }
+        }
+
+        public void Clear()
+        {
+            LayerBitmap.Clear();
         }
         }
 
 
         public byte[] ConvertBitmapToBytes()
         public byte[] ConvertBitmapToBytes()
-        {            
+        {
             LayerBitmap.Lock();
             LayerBitmap.Lock();
             byte[] byteArray = LayerBitmap.ToByteArray();
             byte[] byteArray = LayerBitmap.ToByteArray();
             LayerBitmap.Unlock();
             LayerBitmap.Unlock();
             return byteArray;
             return byteArray;
         }
         }
 
 
-        public byte[] ConvertBitmapToBytes(WriteableBitmap bitmap)
+        public void ResizeCanvas(int offsetX, int offsetY, int offsetXSrc, int offsetYSrc, int newWidth, int newHeight)
         {
         {
-            bitmap.Lock();
-            byte[] byteArray = bitmap.ToByteArray();
-            bitmap.Unlock();
-            return byteArray;
-        }
+            int iteratorHeight = Height > newHeight ? newHeight : Height;
+            int count = Width > newWidth ? newWidth : Width;
 
 
-        public void Resize(int newWidth, int newHeight)
-        {
-            LayerBitmap.Resize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-            Height = newHeight;
-            Width = newWidth;
-        }
+            using (var srcContext = LayerBitmap.GetBitmapContext(ReadWriteMode.ReadOnly))
+            {
+                var result = BitmapFactory.New(newWidth, newHeight);
+                using (var destContext = result.GetBitmapContext())
+                {
+                    for (int line = 0; line < iteratorHeight; line++)
+                    {
+                        var srcOff = ((offsetYSrc + line) * Width + offsetXSrc) * SizeOfArgb;
+                        var dstOff = ((offsetY + line) * newWidth + offsetX) * SizeOfArgb;
+                        BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, count * SizeOfArgb);
+                    }
 
 
+                    LayerBitmap = result;
+                    Width = newWidth;
+                    Height = newHeight;
+                }
+            }
+        }
     }
     }
-}
+}

+ 4 - 24
PixiEditor/Models/Layers/LayerGenerator.cs

@@ -1,21 +1,11 @@
-using PixiEditor.Models.Tools;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
+using System.Windows.Media.Imaging;
 
 
 namespace PixiEditor.Models.Layers
 namespace PixiEditor.Models.Layers
 {
 {
     public static class LayerGenerator
     public static class LayerGenerator
     {
     {
         /// <summary>
         /// <summary>
-        /// Generating useable layer with image and bitmap
+        ///     Generating useable layer with image and bitmap
         /// </summary>
         /// </summary>
         /// <param name="imageWidth">Width of layer.</param>
         /// <param name="imageWidth">Width of layer.</param>
         /// <param name="imageHeight">Height of layer.</param>
         /// <param name="imageHeight">Height of layer.</param>
@@ -25,17 +15,8 @@ namespace PixiEditor.Models.Layers
             return new Layer(GenerateBitmap(imageWidth, imageHeight));
             return new Layer(GenerateBitmap(imageWidth, imageHeight));
         }
         }
 
 
-        public static LightLayer GenerateWithByteArray(int width, int height)
-        {
-            WriteableBitmap bitmap = GenerateBitmap(width, height);
-            bitmap.Lock();
-            byte[] byteArray = bitmap.ToByteArray();
-            bitmap.Unlock();
-            return new LightLayer(byteArray, height, width);
-        }
-
         /// <summary>
         /// <summary>
-        /// Generates bitmap ready to work with
+        ///     Generates bitmap ready to work with
         /// </summary>
         /// </summary>
         /// <param name="bitmapWidth">Width of bitmap.</param>
         /// <param name="bitmapWidth">Width of bitmap.</param>
         /// <param name="imageHeight">Height of bitmap.</param>
         /// <param name="imageHeight">Height of bitmap.</param>
@@ -46,6 +27,5 @@ namespace PixiEditor.Models.Layers
             bitmap.Clear(System.Windows.Media.Colors.Transparent);
             bitmap.Clear(System.Windows.Media.Colors.Transparent);
             return bitmap;
             return bitmap;
         }
         }
-
     }
     }
-}
+}

+ 0 - 54
PixiEditor/Models/Layers/LightLayer.cs

@@ -1,54 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Controls;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using PixiEditor.Helpers;
-
-namespace PixiEditor.Models.Layers
-{
-    [Serializable]
-    public class LightLayer : BasicLayer
-    {
-        private byte[] _layerBytes;
-
-        public byte[] LayerBytes
-        {
-            get => _layerBytes;
-            set
-            {
-                _layerBytes = value;
-                RaisePropertyChanged("LayerBytes");
-            }
-        }
-
-        public LightLayer(int width, int height)
-        {
-            LightLayer layer = LayerGenerator.GenerateWithByteArray(width, height);
-            LayerBytes = layer.LayerBytes;
-            Width = width;
-            Height = height;
-        }
-
-        public LightLayer(byte[] layerBytes, int height, int width)
-        {
-            LayerBytes = layerBytes;
-            Width = height;
-            Height = width;
-        }
-
-        public LightLayer()
-        {
-
-        }
-
-        public static LightLayer Deserialize(object value)
-        {
-            return JsonConvert.DeserializeObject<LightLayer>(((JObject)value).ToString());
-        }
-
-    }
-}

+ 29 - 0
PixiEditor/Models/Layers/SerializableLayer.cs

@@ -0,0 +1,29 @@
+using System;
+
+namespace PixiEditor.Models.Layers
+{
+    [Serializable]
+    public class SerializableLayer
+    {
+        public string Name { get; set; }
+        public int Width { get; set; }
+        public int Height { get; set; }
+        public byte[] BitmapBytes { get; set; }
+        public bool IsVisible { get; set; }
+        public int OffsetX { get; set; }
+        public int OffsetY { get; set; }
+        public float Opacity { get; set; }
+
+        public SerializableLayer(Layer layer)
+        {
+            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;
+        }
+    }
+}

+ 9 - 13
PixiEditor/Models/Position/Coordinates.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Position
+namespace PixiEditor.Models.Position
 {
 {
     public struct Coordinates
     public struct Coordinates
     {
     {
@@ -18,11 +12,13 @@ namespace PixiEditor.Models.Position
             Y = y;
             Y = y;
         }
         }
 
 
-        public override string ToString() => $"x: {X}, y: {Y}";
+        public override string ToString()
+        {
+            return $"x: {X}, y: {Y}";
+        }
 
 
         public static bool operator ==(Coordinates c1, Coordinates c2)
         public static bool operator ==(Coordinates c1, Coordinates c2)
         {
         {
-            if (c1 == null || c2 == null) return false;
             return c2.X == c1.X && c2.Y == c1.Y;
             return c2.X == c1.X && c2.Y == c1.Y;
         }
         }
 
 
@@ -34,21 +30,21 @@ namespace PixiEditor.Models.Position
         public override bool Equals(object obj)
         public override bool Equals(object obj)
         {
         {
             if (obj.GetType() != typeof(Coordinates)) return false;
             if (obj.GetType() != typeof(Coordinates)) return false;
-            return this == (Coordinates)obj;
+            return this == (Coordinates) obj;
         }
         }
 
 
         public override int GetHashCode()
         public override int GetHashCode()
         {
         {
             unchecked
             unchecked
             {
             {
-                const int HashingBase = (int)2166136261;
+                const int HashingBase = (int) 2166136261;
                 const int HashingMultiplier = 16777619;
                 const int HashingMultiplier = 16777619;
 
 
                 int hash = HashingBase;
                 int hash = HashingBase;
                 hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, X) ? X.GetHashCode() : 0);
                 hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, X) ? X.GetHashCode() : 0);
-                hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, Y) ? Y.GetHashCode() : 0);            
+                hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, Y) ? Y.GetHashCode() : 0);
                 return hash;
                 return hash;
             }
             }
         }
         }
     }
     }
-}
+}

+ 172 - 58
PixiEditor/Models/Position/CoordinatesCalculator.cs

@@ -1,62 +1,176 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.Position
-{
-    public static class CoordinatesCalculator
-    {
-        /// <summary>
-        /// Calculates center of thickness * thickness rectangle
-        /// </summary>
-        /// <param name="startPosition">Top left position of rectangle</param>
-        /// <param name="thickness">Thickness of rectangle</param>
-        /// <returns></returns>
-        public static DoubleCords CalculateThicknessCenter(Coordinates startPosition, int thickness)
-        {
-            int x1, x2, y1, y2;
-            if (thickness % 2 == 0)
-            {
-                x2 = startPosition.X + thickness / 2;
-                y2 = startPosition.Y + thickness / 2;
-                x1 = x2 - thickness;
-                y1 = y2 - thickness;
-            }
-            else
-            {
-                x2 = startPosition.X + (thickness - 1) / 2 + 1;
-                y2 = startPosition.Y + (thickness - 1) / 2 + 1;
-                x1 = x2 - thickness;
-                y1 = y2 - thickness;
-            }
-            return new DoubleCords(new Coordinates(x1, y1), new Coordinates(x2 - 1, y2 - 1));
-        }
-
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+
+namespace PixiEditor.Models.Position
+{
+    public static class CoordinatesCalculator
+    {
+        /// <summary>
+        ///     Calculates center of thickness * thickness rectangle
+        /// </summary>
+        /// <param name="startPosition">Top left position of rectangle</param>
+        /// <param name="thickness">Thickness of rectangle</param>
+        /// <returns></returns>
+        public static DoubleCords CalculateThicknessCenter(Coordinates startPosition, int thickness)
+        {
+            int x1, x2, y1, y2;
+            if (thickness % 2 == 0)
+            {
+                x2 = startPosition.X + thickness / 2;
+                y2 = startPosition.Y + thickness / 2;
+                x1 = x2 - thickness;
+                y1 = y2 - thickness;
+            }
+            else
+            {
+                x2 = startPosition.X + (thickness - 1) / 2 + 1;
+                y2 = startPosition.Y + (thickness - 1) / 2 + 1;
+                x1 = x2 - thickness;
+                y1 = y2 - thickness;
+            }
+
+            return new DoubleCords(new Coordinates(x1, y1), new Coordinates(x2 - 1, y2 - 1));
+        }
+
         public static Coordinates GetCenterPoint(Coordinates startingPoint, Coordinates endPoint)
         public static Coordinates GetCenterPoint(Coordinates startingPoint, Coordinates endPoint)
         {
         {
-            int x = (int)Math.Floor((startingPoint.X + endPoint.X) / 2f);
-            int y = (int)Math.Floor((startingPoint.Y + endPoint.Y) / 2f);
+            int x = (int) Math.Floor((startingPoint.X + endPoint.X) / 2f);
+            int y = (int) Math.Floor((startingPoint.Y + endPoint.Y) / 2f);
             return new Coordinates(x, y);
             return new Coordinates(x, y);
-        }
-
-        public static Coordinates[] RectangleToCoordinates(int x1, int y1, int x2, int y2)
-        {
-            x2++;
-            y2++;
-            List<Coordinates> coordinates = new List<Coordinates>();
-            for (int y = y1; y < y1 + (y2 - y1); y++)
-            {
-                for (int x = x1; x < x1 + (x2 - x1); x++)
-                {
-                    coordinates.Add(new Coordinates(x, y));
-                }
-            }
-            return coordinates.ToArray();
-        }
-
-        public static Coordinates[] RectangleToCoordinates(DoubleCords coordinates)
-        {
-            return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X, coordinates.Coords2.Y);
-        }
-    }
+        }
+
+        public static Coordinates[] RectangleToCoordinates(int x1, int y1, int x2, int y2)
+        {
+            x2++;
+            y2++;
+            List<Coordinates> coordinates = new List<Coordinates>();
+            for (int y = y1; y < y1 + (y2 - y1); y++)
+            for (int x = x1; x < x1 + (x2 - x1); x++)
+                coordinates.Add(new Coordinates(x, y));
+            return coordinates.ToArray();
+        }
+
+        public static Coordinates[] RectangleToCoordinates(DoubleCords coordinates)
+        {
+            return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X,
+                coordinates.Coords2.Y);
+        }
+
+        /// <summary>
+        ///     Returns first pixel coordinates in bitmap that is most top left on canvas
+        /// </summary>
+        /// <param name="bitmap"></param>
+        /// <returns></returns>
+        public static Coordinates FindMinEdgeNonTransparentPixel(WriteableBitmap bitmap)
+        {
+            return new Coordinates(FindMinXNonTransparent(bitmap), FindMinYNonTransparent(bitmap));
+        }
+
+        /// <summary>
+        ///     Returns last pixel coordinates that is most bottom right
+        /// </summary>
+        /// <param name="bitmap"></param>
+        /// <returns></returns>
+        public static Coordinates FindMostEdgeNonTransparentPixel(WriteableBitmap bitmap)
+        {
+            return new Coordinates(FindMaxXNonTransparent(bitmap), FindMaxYNonTransparent(bitmap));
+        }
+
+
+        public static int FindMinYNonTransparent(WriteableBitmap bitmap)
+        {
+            Color transparent = Color.FromArgb(0, 0, 0, 0);
+            using var ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
+            for (int y = 0; y < ctx.Height; y++)
+            for (int x = 0; x < ctx.Width; x++)
+                if (ctx.WriteableBitmap.GetPixel(x, y) != transparent)
+                    return y;
+
+            return -1;
+        }
+
+        public static int FindMinXNonTransparent(WriteableBitmap bitmap)
+        {
+            Color transparent = Color.FromArgb(0, 0, 0, 0);
+            using var ctx = bitmap.GetBitmapContext(ReadWriteMode.ReadOnly);
+            for (int x = 0; x < ctx.Width; x++)
+            for (int y = 0; y < ctx.Height; y++)
+                if (bitmap.GetPixel(x, y) != transparent)
+                    return x;
+
+            return -1;
+        }
+
+        public static int FindMaxYNonTransparent(WriteableBitmap bitmap)
+        {
+            Color transparent = Color.FromArgb(0, 0, 0, 0);
+            bitmap.Lock();
+            for (int y = (int) bitmap.Height - 1; y >= 0; y--)
+            for (int x = (int) bitmap.Width - 1; x >= 0; x--)
+                if (bitmap.GetPixel(x, y) != transparent)
+                {
+                    bitmap.Unlock();
+                    return y;
+                }
+
+            bitmap.Unlock();
+            return -1;
+        }
+
+        public static int FindMaxXNonTransparent(WriteableBitmap bitmap)
+        {
+            Color transparent = Color.FromArgb(0, 0, 0, 0);
+            bitmap.Lock();
+            for (int x = (int) bitmap.Width - 1; x >= 0; x--)
+            for (int y = (int) bitmap.Height - 1; y >= 0; y--)
+                if (bitmap.GetPixel(x, y) != transparent)
+                {
+                    bitmap.Unlock();
+                    return x;
+                }
+
+            bitmap.Unlock();
+            return -1;
+        }
+
+        /// <summary>
+        ///     Finds most top-left pixel on each layer.
+        /// </summary>
+        /// <param name="document"></param>
+        /// <returns>Most top-left pixel in each layer</returns>
+        public static Coordinates[] GetSmallestPixels(Document document)
+        {
+            Coordinates[] smallestPixels = new Coordinates[document.Layers.Count];
+            for (int i = 0; i < smallestPixels.Length; i++)
+            {
+                Coordinates point = FindMinEdgeNonTransparentPixel(document.Layers[i].LayerBitmap);
+                if (point.X >= 0 && point.Y >= 0)
+                    smallestPixels[i] = point;
+            }
+
+            return smallestPixels;
+        }
+
+        /// <summary>
+        ///     Finds most bottom-right pixel on each layer.
+        /// </summary>
+        /// <param name="document"></param>
+        /// <returns>Most bottom-right pixel in each layer</returns>
+        public static Coordinates[] GetBiggestPixels(Document document)
+        {
+            Coordinates[] biggestPixels = new Coordinates[document.Layers.Count];
+            for (int i = 0; i < biggestPixels.Length; i++)
+            {
+                Coordinates point = FindMostEdgeNonTransparentPixel(document.Layers[i].LayerBitmap);
+                if (point.X >= 0 && point.Y >= 0)
+                    biggestPixels[i] = point;
+            }
+
+            return biggestPixels;
+        }
+    }
 }
 }

+ 2 - 8
PixiEditor/Models/Position/DoubleCords.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Position
+namespace PixiEditor.Models.Position
 {
 {
     public struct DoubleCords
     public struct DoubleCords
     {
     {
@@ -17,4 +11,4 @@ namespace PixiEditor.Models.Position
             Coords2 = cords2;
             Coords2 = cords2;
         }
         }
     }
     }
-}
+}

+ 11 - 11
PixiEditor/Models/Position/MousePositionConverter.cs

@@ -1,6 +1,6 @@
-using PixiEditor.Models.Layers;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows;
+using PixiEditor.Models.Layers;
 
 
 namespace PixiEditor.Models.Position
 namespace PixiEditor.Models.Position
 {
 {
@@ -10,19 +10,19 @@ namespace PixiEditor.Models.Position
 
 
         public static Coordinates MousePositionToCoordinates(Layer baseLayer, Point mousePosition)
         public static Coordinates MousePositionToCoordinates(Layer baseLayer, Point mousePosition)
         {
         {
-            int xCoord = (int)(mousePosition.X / baseLayer.Width);
-            int yCoord = (int)(mousePosition.Y / baseLayer.Height);
+            int xCoord = (int) (mousePosition.X / baseLayer.Width);
+            int yCoord = (int) (mousePosition.Y / baseLayer.Height);
             return new Coordinates(xCoord, yCoord);
             return new Coordinates(xCoord, yCoord);
         }
         }
 
 
         [DllImport("user32.dll")]
         [DllImport("user32.dll")]
         private static extern bool GetCursorPos(out System.Drawing.Point point);
         private static extern bool GetCursorPos(out System.Drawing.Point point);
-        
-        public static System.Drawing.Point GetCursorPosition()
-        {
-            System.Drawing.Point point;
-            GetCursorPos(out point);
-            return point;
+
+        public static System.Drawing.Point GetCursorPosition()
+        {
+            System.Drawing.Point point;
+            GetCursorPos(out point);
+            return point;
         }
         }
     }
     }
-}
+}

+ 19 - 0
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -0,0 +1,19 @@
+using System.Windows.Media;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.Tools
+{
+    public abstract class BitmapOperationTool : Tool
+    {
+        public bool RequiresPreviewLayer { get; set; }
+        public bool UseDefaultUndoMethod { get; set; } = true;
+        public abstract LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color);
+
+        protected LayerChange[] Only(BitmapPixelChanges changes, Layer layer)
+        {
+            return new[] { new LayerChange(changes, layer) };
+        }
+    }
+}

+ 0 - 29
PixiEditor/Models/Tools/BitmapPixelChanges.cs

@@ -1,29 +0,0 @@
-using PixiEditor.Models.Position;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows.Media;
-
-namespace PixiEditor.Models.Tools
-{
-    public struct BitmapPixelChanges
-    {
-        public Dictionary<Coordinates, Color> ChangedPixels { get; set; } 
-
-
-        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
-        {
-            ChangedPixels = changedPixels;
-        }
-
-        public static BitmapPixelChanges FromSingleColoredArray(Coordinates[] coordinates, Color color)
-        {
-            Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
-            for (int i = 0; i < coordinates.Length; i++)
-            {
-                dict.Add(coordinates[i], color);
-            }
-            return new BitmapPixelChanges(dict);
-        }
-    }
-}

+ 9 - 0
PixiEditor/Models/Tools/ReadonlyTool.cs

@@ -0,0 +1,9 @@
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.Tools
+{
+    public abstract class ReadonlyTool : Tool
+    {
+        public abstract void Use(Coordinates[] pixels);
+    }
+}

+ 41 - 47
PixiEditor/Models/Tools/ShapeTool.cs

@@ -1,63 +1,57 @@
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.Tools;
-using PixiEditor.Models.Tools.ToolSettings;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows.Input;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media;
+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
 namespace PixiEditor.Models.Tools
 {
 {
-    public abstract class ShapeTool : Tool
+    public abstract class ShapeTool : BitmapOperationTool
     {
     {
-        public override abstract ToolType ToolType { get; }
+        public abstract override ToolType ToolType { get; }
 
 
-        public abstract override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color);
-
-        public ShapeTool()
-        {
-            RequiresPreviewLayer = true;
-            Cursor = Cursors.Cross;
-            Toolbar = new BasicShapeToolbar();
+        public ShapeTool()
+        {
+            RequiresPreviewLayer = true;
+            Cursor = Cursors.Cross;
+            Toolbar = new BasicShapeToolbar();
         }
         }
 
 
-        protected Coordinates[] GetThickShape(Coordinates[] shape, int thickness)
-        {
-            List<Coordinates> output = new List<Coordinates>();
-            for (int i = 0; i < shape.Length; i++)
-            {
-                output.AddRange(CoordinatesCalculator.RectangleToCoordinates(CoordinatesCalculator.CalculateThicknessCenter(shape[i], thickness)));
-            }
-            return output.Distinct().ToArray();
+        public abstract override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color);
+
+        protected Coordinates[] GetThickShape(Coordinates[] shape, int thickness)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            for (int i = 0; i < shape.Length; i++)
+                output.AddRange(
+                    CoordinatesCalculator.RectangleToCoordinates(
+                        CoordinatesCalculator.CalculateThicknessCenter(shape[i], thickness)));
+            return output.Distinct().ToArray();
         }
         }
-     
 
 
-        protected DoubleCords CalculateCoordinatesForShapeRotation(Coordinates startingCords, Coordinates secondCoordinates)
+
+        protected DoubleCords CalculateCoordinatesForShapeRotation(Coordinates startingCords,
+            Coordinates secondCoordinates)
         {
         {
             Coordinates currentCoordinates = secondCoordinates;
             Coordinates currentCoordinates = secondCoordinates;
 
 
             if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
             if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
-            {
-                return new DoubleCords(new Coordinates(currentCoordinates.X, currentCoordinates.Y), new Coordinates(startingCords.X, startingCords.Y));
-            }
-            else if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
-            {
-                return new DoubleCords(new Coordinates(startingCords.X, startingCords.Y), new Coordinates(currentCoordinates.X, currentCoordinates.Y));
-            }
-            else if (startingCords.Y > currentCoordinates.Y)
-            {
-                return new DoubleCords(new Coordinates(startingCords.X, currentCoordinates.Y), new Coordinates(currentCoordinates.X, startingCords.Y));
-            }
-            else if(startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
-            {
-                return new DoubleCords(new Coordinates(currentCoordinates.X, startingCords.Y), new Coordinates(startingCords.X, currentCoordinates.Y));
-            }
-            else
-            {
-                return new DoubleCords(startingCords, secondCoordinates);
-            }
+                return new DoubleCords(new Coordinates(currentCoordinates.X, currentCoordinates.Y),
+                    new Coordinates(startingCords.X, startingCords.Y));
+            if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
+                return new DoubleCords(new Coordinates(startingCords.X, startingCords.Y),
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y));
+            if (startingCords.Y > currentCoordinates.Y)
+                return new DoubleCords(new Coordinates(startingCords.X, currentCoordinates.Y),
+                    new Coordinates(currentCoordinates.X, startingCords.Y));
+            if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
+                return new DoubleCords(new Coordinates(currentCoordinates.X, startingCords.Y),
+                    new Coordinates(startingCords.X, currentCoordinates.Y));
+            return new DoubleCords(startingCords, secondCoordinates);
         }
         }
     }
     }
-}
+}

+ 27 - 21
PixiEditor/Models/Tools/Tool.cs

@@ -1,34 +1,40 @@
-using PixiEditor.Helpers;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings;
-using System.Windows.Input;
-using System.Windows.Media;
+using System.Windows.Input;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Tools.ToolSettings;
 
 
 namespace PixiEditor.Models.Tools
 namespace PixiEditor.Models.Tools
 {
 {
     public abstract class Tool : NotifyableObject
     public abstract class Tool : NotifyableObject
     {
     {
-        public abstract BitmapPixelChanges Use(Layer layer, Coordinates[] pixels, Color color);
         public abstract ToolType ToolType { get; }
         public abstract ToolType ToolType { get; }
         public string ImagePath => $"/Images/{ToolType}Image.png";
         public string ImagePath => $"/Images/{ToolType}Image.png";
-        public bool PerformsOperationOnBitmap { get; set; } = true;
         public bool HideHighlight { get; set; } = false;
         public bool HideHighlight { get; set; } = false;
-        public bool RequiresPreviewLayer { get; set; }
         public string Tooltip { get; set; }
         public string Tooltip { get; set; }
 
 
-        private bool _isActive = false;
-        public bool IsActive
-        {
-            get { return _isActive; }
-            set 
-            { 
-                _isActive = value;
-                RaisePropertyChanged("IsActive");
-            }
-        }
-
+        public bool IsActive
+        {
+            get => _isActive;
+            set
+            {
+                _isActive = value;
+                RaisePropertyChanged("IsActive");
+            }
+        }
+
         public Cursor Cursor { get; set; } = Cursors.Arrow;
         public Cursor Cursor { get; set; } = Cursors.Arrow;
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
+        private bool _isActive;
+
+        public virtual void OnMouseDown()
+        {
+        }
+
+        public virtual void OnMouseUp()
+        {
+        }
+
+        public virtual void AfterAddedUndo()
+        {
+        }
     }
     }
-}
+}

+ 8 - 6
PixiEditor/Models/Tools/ToolSettings/Settings/BoolSetting.cs

@@ -1,4 +1,6 @@
-using System.Windows.Controls;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
 using System.Windows.Data;
 using System.Windows.Data;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
@@ -21,10 +23,10 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 
 
         private Control GenerateCheckBox()
         private Control GenerateCheckBox()
         {
         {
-            CheckBox checkBox = new CheckBox()
+            CheckBox checkBox = new CheckBox
             {
             {
-                IsChecked = (bool)Value,
-                VerticalAlignment = System.Windows.VerticalAlignment.Center
+                IsChecked = (bool) Value,
+                VerticalAlignment = VerticalAlignment.Center
             };
             };
 
 
             Binding binding = new Binding("Value")
             Binding binding = new Binding("Value")
@@ -32,9 +34,9 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
                 Mode = BindingMode.TwoWay
                 Mode = BindingMode.TwoWay
             };
             };
 
 
-            checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
+            checkBox.SetBinding(ToggleButton.IsCheckedProperty, binding);
 
 
             return checkBox;
             return checkBox;
         }
         }
     }
     }
-}
+}

+ 6 - 11
PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs

@@ -1,6 +1,6 @@
 using System.Windows.Data;
 using System.Windows.Data;
 using System.Windows.Media;
 using System.Windows.Media;
-using Xceed.Wpf.Toolkit;
+using PixiEditor.Views;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
 {
@@ -13,20 +13,15 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
             Value = Color.FromArgb(0, 0, 0, 0);
             Value = Color.FromArgb(0, 0, 0, 0);
         }
         }
 
 
-        private ColorPicker GenerateColorPicker()
+        private PortableColorPicker GenerateColorPicker()
         {
         {
-            ColorPicker picker = new ColorPicker()
-            {
-                UsingAlphaChannel = true,
-                AvailableColorsSortingMode = ColorSortingMode.Alphabetical,
-                Width = 70
-            };
+            PortableColorPicker picker = new PortableColorPicker();
             Binding binding = new Binding("Value")
             Binding binding = new Binding("Value")
             {
             {
-                Mode = BindingMode.TwoWay,
+                Mode = BindingMode.TwoWay
             };
             };
-            picker.SetBinding(ColorPicker.SelectedColorProperty, binding);
+            picker.SetBinding(PortableColorPicker.SelectedColorProperty, binding);
             return picker;
             return picker;
         }
         }
     }
     }
-}
+}

+ 49 - 0
PixiEditor/Models/Tools/ToolSettings/Settings/DropdownSetting.cs

@@ -0,0 +1,49 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Settings
+{
+    public class DropdownSetting : Setting
+    {
+        public string[] Values { get; set; }
+
+        public DropdownSetting(string name, string[] values, string label) : base(name)
+        {
+            Values = values;
+            SettingControl = GenerateDropdown();
+            Value = ((ComboBox) SettingControl).Items[0];
+            Label = label;
+        }
+
+
+        private ComboBox GenerateDropdown()
+        {
+            ComboBox combobox = new ComboBox
+            {
+                VerticalAlignment = VerticalAlignment.Center
+            };
+            GenerateItems(combobox);
+
+            Binding binding = new Binding("Value")
+            {
+                Mode = BindingMode.TwoWay
+            };
+            combobox.SetBinding(Selector.SelectedValueProperty, binding);
+            return combobox;
+        }
+
+        private void GenerateItems(ComboBox comboBox)
+        {
+            for (int i = 0; i < Values.Length; i++)
+            {
+                ComboBoxItem item = new ComboBoxItem
+                {
+                    Content = Values[i]
+                };
+                comboBox.Items.Add(item);
+            }
+        }
+    }
+}

+ 6 - 14
PixiEditor/Models/Tools/ToolSettings/Settings/FloatSetting.cs

@@ -1,21 +1,14 @@
-using PixiEditor.Views;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
+using System.Windows.Data;
+using PixiEditor.Views;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
 {
     public class FloatSetting : Setting
     public class FloatSetting : Setting
     {
     {
-
         public float Min { get; set; }
         public float Min { get; set; }
         public float Max { get; set; }
         public float Max { get; set; }
 
 
-        public FloatSetting(string name, float initialValue, string label = "", 
+        public FloatSetting(string name, float initialValue, string label = "",
             float min = float.NegativeInfinity, float max = float.PositiveInfinity) : base(name)
             float min = float.NegativeInfinity, float max = float.PositiveInfinity) : base(name)
         {
         {
             Label = label;
             Label = label;
@@ -27,20 +20,19 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 
 
         private NumberInput GenerateNumberInput()
         private NumberInput GenerateNumberInput()
         {
         {
-            NumberInput numbrInput = new NumberInput()
+            NumberInput numbrInput = new NumberInput
             {
             {
                 Width = 40,
                 Width = 40,
                 Height = 20,
                 Height = 20,
                 Min = Min,
                 Min = Min,
                 Max = Max
                 Max = Max
-
             };
             };
             Binding binding = new Binding("Value")
             Binding binding = new Binding("Value")
             {
             {
-                Mode = BindingMode.TwoWay,
+                Mode = BindingMode.TwoWay
             };
             };
             numbrInput.SetBinding(NumberInput.ValueProperty, binding);
             numbrInput.SetBinding(NumberInput.ValueProperty, binding);
             return numbrInput;
             return numbrInput;
         }
         }
     }
     }
-}
+}

+ 10 - 9
PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs

@@ -1,8 +1,5 @@
-using PixiEditor.Helpers;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows.Controls;
+using System.Windows.Controls;
+using PixiEditor.Helpers;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings
 namespace PixiEditor.Models.Tools.ToolSettings
 {
 {
@@ -11,19 +8,23 @@ namespace PixiEditor.Models.Tools.ToolSettings
         public string Name { get; protected set; }
         public string Name { get; protected set; }
         public string Label { get; set; }
         public string Label { get; set; }
         public bool HasLabel => !string.IsNullOrEmpty(Label);
         public bool HasLabel => !string.IsNullOrEmpty(Label);
-        private object value;
-        public object Value { get => value;
+
+        public object Value
+        {
+            get => value;
             set
             set
             {
             {
                 this.value = value;
                 this.value = value;
                 RaisePropertyChanged("Value");
                 RaisePropertyChanged("Value");
             }
             }
         }
         }
+
         public Control SettingControl { get; set; }
         public Control SettingControl { get; set; }
+        private object value;
 
 
         public Setting(string name)
         public Setting(string name)
         {
         {
             Name = name;
             Name = name;
         }
         }
-	}
-}
+    }
+}

+ 6 - 9
PixiEditor/Models/Tools/ToolSettings/Settings/SizeSetting.cs

@@ -1,12 +1,9 @@
-using PixiEditor.Helpers;
-using PixiEditor.Helpers.Behaviours;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows;
+using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Data;
 using System.Windows.Interactivity;
 using System.Windows.Interactivity;
+using PixiEditor.Helpers;
+using PixiEditor.Helpers.Behaviours;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
 {
@@ -21,7 +18,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 
 
         private TextBox GenerateTextBox()
         private TextBox GenerateTextBox()
         {
         {
-            TextBox tb = new TextBox()
+            TextBox tb = new TextBox
             {
             {
                 Style = Application.Current.FindResource("DarkTextBoxStyle") as Style,
                 Style = Application.Current.FindResource("DarkTextBoxStyle") as Style,
                 TextAlignment = TextAlignment.Center,
                 TextAlignment = TextAlignment.Center,
@@ -32,7 +29,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
             Binding binding = new Binding("Value")
             Binding binding = new Binding("Value")
             {
             {
                 Converter = new ToolSizeToIntConverter(),
                 Converter = new ToolSizeToIntConverter(),
-                Mode = BindingMode.TwoWay,
+                Mode = BindingMode.TwoWay
             };
             };
             tb.SetBinding(TextBox.TextProperty, binding);
             tb.SetBinding(TextBox.TextProperty, binding);
             TextBoxFocusBehavior behavor = new TextBoxFocusBehavior
             TextBoxFocusBehavior behavor = new TextBoxFocusBehavior
@@ -43,4 +40,4 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
             return tb;
             return tb;
         }
         }
     }
     }
-}
+}

+ 3 - 6
PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicShapeToolbar.cs

@@ -1,16 +1,13 @@
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
-using System;
-using System.Collections.Generic;
-using System.Text;
 
 
-namespace PixiEditor.Models.Tools.ToolSettings
+namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
 {
     public class BasicShapeToolbar : BasicToolbar
     public class BasicShapeToolbar : BasicToolbar
     {
     {
         public BasicShapeToolbar()
         public BasicShapeToolbar()
         {
         {
             Settings.Add(new BoolSetting("Fill", "Fill shape: "));
             Settings.Add(new BoolSetting("Fill", "Fill shape: "));
-            Settings.Add(new ColorSetting("FillColor", "Fill color"));            
+            Settings.Add(new ColorSetting("FillColor", "Fill color"));
         }
         }
     }
     }
-}
+}

+ 2 - 7
PixiEditor/Models/Tools/ToolSettings/Toolbars/BasicToolbar.cs

@@ -1,11 +1,6 @@
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Text;
-using System.Windows.Controls;
 
 
-namespace PixiEditor.Models.Tools.ToolSettings
+namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
 {
     public class BasicToolbar : Toolbar
     public class BasicToolbar : Toolbar
     {
     {
@@ -14,4 +9,4 @@ namespace PixiEditor.Models.Tools.ToolSettings
             Settings.Add(new SizeSetting("ToolSize", "Tool size:"));
             Settings.Add(new SizeSetting("ToolSize", "Tool size:"));
         }
         }
     }
     }
-}
+}

+ 5 - 5
PixiEditor/Models/Tools/ToolSettings/Toolbars/BrightnessToolToolbar.cs

@@ -1,7 +1,6 @@
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
 {
@@ -10,6 +9,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         public BrightnessToolToolbar(float initialValue)
         public BrightnessToolToolbar(float initialValue)
         {
         {
             Settings.Add(new FloatSetting("CorrectionFactor", initialValue, "Strength:", 0f, 100f));
             Settings.Add(new FloatSetting("CorrectionFactor", initialValue, "Strength:", 0f, 100f));
+            Settings.Add(new DropdownSetting("Mode", Enum.GetNames(typeof(BrightnessMode)), "Mode"));
         }
         }
     }
     }
-}
+}

+ 2 - 7
PixiEditor/Models/Tools/ToolSettings/Toolbars/EmptyToolbar.cs

@@ -1,11 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace PixiEditor.Models.Tools.ToolSettings
+namespace PixiEditor.Models.Tools.ToolSettings
 {
 {
     public class EmptyToolbar : Toolbar
     public class EmptyToolbar : Toolbar
     {
     {
-
     }
     }
-}
+}

+ 12 - 0
PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs

@@ -0,0 +1,12 @@
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
+{
+    public class SelectToolToolbar : Toolbar
+    {
+        public SelectToolToolbar()
+        {
+            Settings.Add(new DropdownSetting("Mode", new[] {"New", "Add", "Subtract"}, "Selection type"));
+        }
+    }
+}

+ 10 - 18
PixiEditor/Models/Tools/ToolSettings/Toolbars/Toolbar.cs

@@ -1,49 +1,41 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
-using System.Windows.Controls;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings
 namespace PixiEditor.Models.Tools.ToolSettings
 {
 {
     public abstract class Toolbar
     public abstract class Toolbar
     {
     {
+        private static readonly List<Setting> _sharedSettings = new List<Setting>();
         public ObservableCollection<Setting> Settings { get; set; } = new ObservableCollection<Setting>();
         public ObservableCollection<Setting> Settings { get; set; } = new ObservableCollection<Setting>();
-        private static List<Setting> _sharedSettings = new List<Setting>();
 
 
         public virtual Setting GetSetting(string name)
         public virtual Setting GetSetting(string name)
         {
         {
             return Settings.FirstOrDefault(x => x.Name == name);
             return Settings.FirstOrDefault(x => x.Name == name);
         }
         }
 
 
+        public virtual Setting[] GetSettings(string name)
+        {
+            return Settings.Where(x => x.Name == name).ToArray();
+        }
+
         /// <summary>
         /// <summary>
-        /// Saves current toolbar state, so other toolbars with common settings can load them.
+        ///     Saves current toolbar state, so other toolbars with common settings can load them.
         /// </summary>
         /// </summary>
         public void SaveToolbarSettings()
         public void SaveToolbarSettings()
         {
         {
             for (int i = 0; i < Settings.Count; i++)
             for (int i = 0; i < Settings.Count; i++)
-            {
-                if(_sharedSettings.Any(x=> x.Name == Settings[i].Name))
-                {
+                if (_sharedSettings.Any(x => x.Name == Settings[i].Name))
                     _sharedSettings.First(x => x.Name == Settings[i].Name).Value = Settings[i].Value;
                     _sharedSettings.First(x => x.Name == Settings[i].Name).Value = Settings[i].Value;
-                }
                 else
                 else
-                {
                     _sharedSettings.Add(Settings[i]);
                     _sharedSettings.Add(Settings[i]);
-                }
-
-
-            }
         }
         }
 
 
         public void LoadSharedSettings()
         public void LoadSharedSettings()
         {
         {
             for (int i = 0; i < _sharedSettings.Count; i++)
             for (int i = 0; i < _sharedSettings.Count; i++)
-            {
-                if(Settings.Any(x=> x.Name == _sharedSettings[i].Name))
-                {
+                if (Settings.Any(x => x.Name == _sharedSettings[i].Name))
                     Settings.First(x => x.Name == _sharedSettings[i].Name).Value = _sharedSettings[i].Value;
                     Settings.First(x => x.Name == _sharedSettings[i].Name).Value = _sharedSettings[i].Value;
-                }
-            }
         }
         }
     }
     }
-}
+}

+ 13 - 9
PixiEditor/Models/Tools/ToolType.cs

@@ -1,13 +1,17 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PixiEditor.Models.Tools
+namespace PixiEditor.Models.Tools
 {
 {
     public enum ToolType
     public enum ToolType
     {
     {
-        None, Pen, Bucket, Line, Circle, Rectangle, Earser, Brightness, ColorPicker
+        None,
+        Move,
+        Pen,
+        Select,
+        Bucket,
+        Line,
+        Circle,
+        Rectangle,
+        Earser,
+        Brightness,
+        ColorPicker
     }
     }
-}
+}

+ 61 - 30
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -1,51 +1,82 @@
-using PixiEditor.Models.Colors;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
+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.Toolbars;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
-    public class BrightnessTool : Tool
+    public class BrightnessTool : BitmapOperationTool
     {
     {
-        public override ToolType ToolType => ToolType.Brightness;
         private const float CorrectionFactor = 5f; //Initial correction factor
         private const float CorrectionFactor = 5f; //Initial correction factor
-        
-        public BrightnessTool()
-        {
-            Tooltip = "Makes pixel brighter or darker pixel (U)";
-            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
+
+        public override ToolType ToolType => ToolType.Brightness;
+        public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
+
+        private List<Coordinates> _pixelsVisited = new List<Coordinates>();
+
+        public BrightnessTool()
+        {
+            Tooltip = "Makes pixel brighter or darker pixel (U)";
+            Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
         }
 
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color)
+        public override void OnMouseDown()
         {
         {
-            int toolSize = (int)Toolbar.GetSetting("ToolSize").Value;
-            float correctionFactor = (float)Toolbar.GetSetting("CorrectionFactor").Value;
-            if(Keyboard.IsKeyDown(Key.LeftCtrl))
-            {
-                return ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor);
-            }
-                return ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor);
-        }       
+            base.OnMouseDown();
+            _pixelsVisited.Clear();
+        }
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
+        {
+            int toolSize = (int) Toolbar.GetSetting("ToolSize").Value;
+            float correctionFactor = (float) Toolbar.GetSetting("CorrectionFactor").Value;
+            Enum.TryParse<BrightnessMode>((Toolbar.GetSetting("Mode").Value as ComboBoxItem)?.Content as string, out var mode);
+            Mode = mode;
 
 
-        private BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
+            LayerChange[] layersChanges = new LayerChange[1];
+            if (Keyboard.IsKeyDown(Key.LeftCtrl))
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, -correctionFactor),
+                    layer);
+            else
+                layersChanges[0] = new LayerChange(ChangeBrightness(layer, coordinates[0], toolSize, correctionFactor),
+                    layer);
+            return layersChanges;
+        }
+
+        private BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize,
+            float correctionFactor)
         {
         {
             DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
             DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(centeredCoords.Coords1.X, centeredCoords.Coords1.Y,
+            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(centeredCoords.Coords1.X,
+                centeredCoords.Coords1.Y,
                 centeredCoords.Coords2.X, centeredCoords.Coords2.Y);
                 centeredCoords.Coords2.X, centeredCoords.Coords2.Y);
             BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
             BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
 
 
-            for (int i = 0; i < rectangleCoordinates.Length; i++)
-            {
-                Color pixel = layer.LayerBitmap.GetPixel(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
-                Color newColor = ExColor.ChangeColorBrightness(Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B), correctionFactor);
-                changes.ChangedPixels.Add(new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y), newColor);
+            for (int i = 0; i < rectangleCoordinates.Length; i++)
+            {
+                if (Mode == BrightnessMode.Default)
+                {
+                    if(_pixelsVisited.Contains(rectangleCoordinates[i]))
+                        continue;
+                    _pixelsVisited.Add(rectangleCoordinates[i]);
+                }
+
+                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
+                Color newColor = ExColor.ChangeColorBrightness(Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
+                    correctionFactor);
+                changes.ChangedPixels.Add(new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
+                    newColor);
             }
             }
+
             return changes;
             return changes;
         }
         }
     }
     }
-}
+}

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

@@ -1,40 +1,70 @@
-using PixiEditor.Helpers.Extensions;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
-using PixiEditor.Models.Position;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class CircleTool : ShapeTool
-    {
-        public override ToolType ToolType => ToolType.Circle;
-
+using System.Windows.Media;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class CircleTool : ShapeTool
+    {
+        public override ToolType ToolType => ToolType.Circle;
+
         public CircleTool()
         public CircleTool()
         {
         {
             Tooltip = "Draws circle on cavnas (C)";
             Tooltip = "Draws circle on cavnas (C)";
-        }
-
-        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color)
+        }
+
+        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
         {
-            int thickness = (int)Toolbar.GetSetting("ToolSize").Value;
+            int thickness = (int) Toolbar.GetSetting("ToolSize").Value;
             DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
             DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(coordinates[^1], coordinates[0]);
             Coordinates[] outline = CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, thickness);
             Coordinates[] outline = CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, thickness);
             BitmapPixelChanges pixels = BitmapPixelChanges.FromSingleColoredArray(outline, color);
             BitmapPixelChanges pixels = BitmapPixelChanges.FromSingleColoredArray(outline, color);
-            if ((bool)Toolbar.GetSetting("Fill").Value)
+            if ((bool) Toolbar.GetSetting("Fill").Value)
             {
             {
-                Color fillColor = (Color)Toolbar.GetSetting("FillColor").Value;
+                Color fillColor = (Color) Toolbar.GetSetting("FillColor").Value;
                 pixels.ChangedPixels.AddRangeNewOnly(
                 pixels.ChangedPixels.AddRangeNewOnly(
-                    BitmapPixelChanges.FromSingleColoredArray(CalculateFillForEllipse(outline), fillColor).ChangedPixels);
+                    BitmapPixelChanges.FromSingleColoredArray(CalculateFillForEllipse(outline), fillColor)
+                        .ChangedPixels);
+            }
+
+            return new[] {new LayerChange(pixels, layer)};
+        }
+
+        /// <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 Coordinates[] CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness,
+            bool filled)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            Coordinates[] outline = CreateEllipse(startCoordinates, endCoordinates, thickness);
+            output.AddRange(outline);
+            if (filled)
+            {
+                output.AddRange(CalculateFillForEllipse(outline));
+                return output.Distinct().ToArray();
             }
             }
 
 
-            return pixels;
-        }
-
+            return output.ToArray();
+        }
+        /// <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 Coordinates[] CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness)
         public Coordinates[] CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, int thickness)
         {
         {
             Coordinates centerCoordinates = CoordinatesCalculator.GetCenterPoint(startCoordinates, endCoordinates);
             Coordinates centerCoordinates = CoordinatesCalculator.GetCenterPoint(startCoordinates, endCoordinates);
@@ -43,16 +73,12 @@ namespace PixiEditor.Models.Tools.Tools
             List<Coordinates> output = new List<Coordinates>();
             List<Coordinates> output = new List<Coordinates>();
             Coordinates[] ellipse = MidpointEllipse(radiusX, radiusY, centerCoordinates.X, centerCoordinates.Y);
             Coordinates[] ellipse = MidpointEllipse(radiusX, radiusY, centerCoordinates.X, centerCoordinates.Y);
             if (thickness == 1)
             if (thickness == 1)
-            {
                 output.AddRange(ellipse);
                 output.AddRange(ellipse);
-            }
             else
             else
-            {
                 output.AddRange(GetThickShape(ellipse, thickness));
                 output.AddRange(GetThickShape(ellipse, thickness));
-            }
             return output.Distinct().ToArray();
             return output.Distinct().ToArray();
-        }
-
+        }
+
         public Coordinates[] MidpointEllipse(double rx, double ry, double xc, double yc)
         public Coordinates[] MidpointEllipse(double rx, double ry, double xc, double yc)
         {
         {
             List<Coordinates> outputCoordinates = new List<Coordinates>();
             List<Coordinates> outputCoordinates = new List<Coordinates>();
@@ -60,7 +86,7 @@ namespace PixiEditor.Models.Tools.Tools
             x = 0;
             x = 0;
             y = ry;
             y = ry;
 
 
-            d1 = (ry * ry) - (rx * rx * ry) + (0.25f * rx * rx);
+            d1 = ry * ry - rx * rx * ry + 0.25f * rx * rx;
             dx = 2 * ry * ry * x;
             dx = 2 * ry * ry * x;
             dy = 2 * rx * rx * y;
             dy = 2 * rx * rx * y;
 
 
@@ -70,21 +96,21 @@ namespace PixiEditor.Models.Tools.Tools
                 if (d1 < 0)
                 if (d1 < 0)
                 {
                 {
                     x++;
                     x++;
-                    dx += (2 * ry * ry);
-                    d1 = d1 + dx + (ry * ry);
+                    dx += 2 * ry * ry;
+                    d1 = d1 + dx + ry * ry;
                 }
                 }
                 else
                 else
                 {
                 {
                     x++;
                     x++;
                     y--;
                     y--;
-                    dx += (2 * ry * ry);
-                    dy -= (2 * rx * rx);
-                    d1 = d1 + dx - dy + (ry * ry);
+                    dx += 2 * ry * ry;
+                    dy -= 2 * rx * rx;
+                    d1 = d1 + dx - dy + ry * ry;
                 }
                 }
             }
             }
 
 
             //Decision parameter of region 2
             //Decision parameter of region 2
-            d2 = ((ry * ry) * ((x + 0.5f) * (x + 0.5f))) + ((rx * rx) * ((y - 1) * (y - 1))) - (rx * rx * ry * ry);
+            d2 = ry * ry * ((x + 0.5f) * (x + 0.5f)) + rx * rx * ((y - 1) * (y - 1)) - rx * rx * ry * ry;
 
 
             while (y >= 0)
             while (y >= 0)
             {
             {
@@ -93,23 +119,22 @@ namespace PixiEditor.Models.Tools.Tools
                 if (d2 > 0)
                 if (d2 > 0)
                 {
                 {
                     y--;
                     y--;
-                    dy -= (2 * rx * rx);
-                    d2 = d2 + (rx * rx) - dy;
+                    dy -= 2 * rx * rx;
+                    d2 = d2 + rx * rx - dy;
                 }
                 }
                 else
                 else
                 {
                 {
                     y--;
                     y--;
                     x++;
                     x++;
-                    dx += (2 * ry * ry);
-                    dy -= (2 * rx * rx);
-                    d2 = d2 + dx - dy + (rx * rx);
+                    dx += 2 * ry * ry;
+                    dy -= 2 * rx * rx;
+                    d2 = d2 + dx - dy + rx * rx;
                 }
                 }
             }
             }
 
 
             return outputCoordinates.Distinct().ToArray();
             return outputCoordinates.Distinct().ToArray();
+        }
 
 
-        }
-
         private Coordinates[] CalculateFillForEllipse(Coordinates[] outlineCoordinates)
         private Coordinates[] CalculateFillForEllipse(Coordinates[] outlineCoordinates)
         {
         {
             List<Coordinates> finalCoordinates = new List<Coordinates>();
             List<Coordinates> finalCoordinates = new List<Coordinates>();
@@ -120,22 +145,20 @@ namespace PixiEditor.Models.Tools.Tools
                 var rowCords = outlineCoordinates.Where(x => x.Y == i);
                 var rowCords = outlineCoordinates.Where(x => x.Y == i);
                 int right = rowCords.Max(x => x.X);
                 int right = rowCords.Max(x => x.X);
                 int left = rowCords.Min(x => x.X);
                 int left = rowCords.Min(x => x.X);
-                for (int j = left + 1; j < right; j++)
-                {
-                    finalCoordinates.Add(new Coordinates(j, i));
-                }
+                for (int j = left + 1; j < right; j++) finalCoordinates.Add(new Coordinates(j, i));
             }
             }
+
             return finalCoordinates.ToArray();
             return finalCoordinates.ToArray();
         }
         }
 
 
         private Coordinates[] GetRegionPoints(double x, double xc, double y, double yc)
         private Coordinates[] GetRegionPoints(double x, double xc, double y, double yc)
         {
         {
             Coordinates[] outputCoordinates = new Coordinates[4];
             Coordinates[] outputCoordinates = new Coordinates[4];
-            outputCoordinates[0] = (new Coordinates((int)x + (int)xc, (int)y + (int)yc));
-            outputCoordinates[1] = (new Coordinates((int)-x + (int)xc, (int)y + (int)yc));
-            outputCoordinates[2] = (new Coordinates((int)x + (int)xc, (int)-y + (int)yc));
-            outputCoordinates[3] = (new Coordinates((int)-x + (int)xc, (int)-y + (int)yc));
+            outputCoordinates[0] = new Coordinates((int) x + (int) xc, (int) y + (int) yc);
+            outputCoordinates[1] = new Coordinates((int) -x + (int) xc, (int) y + (int) yc);
+            outputCoordinates[2] = new Coordinates((int) x + (int) xc, (int) -y + (int) yc);
+            outputCoordinates[3] = new Coordinates((int) -x + (int) xc, (int) -y + (int) yc);
             return outputCoordinates;
             return outputCoordinates;
         }
         }
-    }
-}
+    }
+}

+ 25 - 10
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -1,25 +1,40 @@
-using PixiEditor.Models.Layers;
+using System.Drawing;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows.Media;
+using PixiEditor.ViewModels;
+using Color = System.Windows.Media.Color;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
-    public class ColorPickerTool : Tool
+    public class ColorPickerTool : ReadonlyTool
     {
     {
         public override ToolType ToolType => ToolType.ColorPicker;
         public override ToolType ToolType => ToolType.ColorPicker;
 
 
         public ColorPickerTool()
         public ColorPickerTool()
         {
         {
-            PerformsOperationOnBitmap = false;
             HideHighlight = true;
             HideHighlight = true;
+            Tooltip = "Swaps primary color with selected on canvas. (O)";
         }
         }
 
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates[] pixels, Color color)
+        public override void Use(Coordinates[] coordinates)
         {
         {
-            return new BitmapPixelChanges();
+            ViewModelMain.Current.PrimaryColor = GetColorUnderMouse();
+        }
+
+        public Color GetColorUnderMouse()
+        {
+            System.Drawing.Color color;
+            using (var bitmap = new Bitmap(1, 1))
+            {
+                using (var graphics = Graphics.FromImage(bitmap))
+                {
+                    graphics.CopyFromScreen(MousePositionConverter.GetCursorPosition(), new Point(0, 0),
+                        new Size(1, 1));
+                }
+
+                color = bitmap.GetPixel(0, 0);
+            }
+
+            return Color.FromArgb(color.A, color.R, color.G, color.B);
         }
         }
     }
     }
-}
+}

+ 15 - 9
PixiEditor/Models/Tools/Tools/EarserTool.cs

@@ -1,14 +1,13 @@
-using PixiEditor.Models.Layers;
+using System.Windows.Media;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows.Media;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
-    public class EarserTool : Tool
+    public class EarserTool : BitmapOperationTool
     {
     {
         public override ToolType ToolType => ToolType.Earser;
         public override ToolType ToolType => ToolType.Earser;
 
 
@@ -18,10 +17,17 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
         }
         }
 
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color)
+        public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
         {
+            return Earse(layer, coordinates, (int) Toolbar.GetSetting("ToolSize").Value);
+        }
+
+        public LayerChange[] Earse(Layer layer, Coordinates[] coordinates, int toolSize)
+        {
+            Coordinates startingCords = coordinates.Length > 1 ? coordinates[1] : coordinates[0];
             PenTool pen = new PenTool();
             PenTool pen = new PenTool();
-            return pen.Draw(coordinates[0], System.Windows.Media.Colors.Transparent, (int)Toolbar.GetSetting("ToolSize").Value);
+            var pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
+            return new[] {new LayerChange(pixels, layer)};
         }
         }
     }
     }
-}
+}

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