Browse Source

Bitmap operations refactoring wip

Frytek 5 years ago
parent
commit
f5329af462

+ 92 - 0
PixiEditorDotNetCore3/Models/Controllers/BitmapOperationsUtility.cs

@@ -0,0 +1,92 @@
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
+using PixiEditorDotNetCore3.Models.Layers;
+using PixiEditorDotNetCore3.Models.Position;
+using PixiEditorDotNetCore3.Models.Tools;
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class BitmapOperationsUtility : NotifyableObject
+    {
+        public MouseMovementController MouseController { get; set; }
+        public Tool SelectedTool { get; set; }
+
+        private ObservableCollection<Layer> _layers;
+
+        public ObservableCollection<Layer> Layers
+        {
+            get => _layers;
+            set { if (_layers != value) { _layers = value; } }
+        }
+        private int _activeLayerIndex;
+        public int ActiveLayerIndex
+        {
+            get => _activeLayerIndex;
+            set
+            {
+                _activeLayerIndex = value;
+                RaisePropertyChanged("ActiveLayerIndex");
+                RaisePropertyChanged("ActiveLayer");
+            }
+        }
+
+        public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
+
+        public Color PrimaryColor { get; set; }
+        
+        public int ToolSize { get; set; }
+
+        public event EventHandler<BitmapChangedEventArgs> BitmapChanged;
+
+        public BitmapOperationsUtility()
+        {
+            Layers = new ObservableCollection<Layer>();
+            MouseController = new MouseMovementController();
+            MouseController.MousePositionChanged += Controller_MousePositionChanged;
+        }
+
+        private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
+        {
+            if(SelectedTool != null && Mouse.LeftButton == MouseButtonState.Pressed)
+            {
+                var pixels = MouseController.LastMouseMoveCoordinates;
+                pixels.Reverse();
+                var changedPixels = SelectedTool.Use(Layers[ActiveLayerIndex], pixels.ToArray(), PrimaryColor, ToolSize);
+                ActiveLayer.ApplyPixels(changedPixels);
+                BitmapChanged?.Invoke(this, new BitmapChangedEventArgs(changedPixels, ActiveLayerIndex));
+            }
+        }
+
+        public void AddNewLayer(string name, int height, int width, bool setAsActive = true)
+        {
+            Layers.Add(new Layer(name, width, height));
+            Layers.Move(Layers.Count - 1, 0);
+            if (setAsActive)
+            {
+                ActiveLayerIndex = 0;
+            }
+        }
+
+        public void SetActiveLayer(int index)
+        {
+            ActiveLayerIndex = index;
+        }
+    }
+}
+
+public class BitmapChangedEventArgs : EventArgs
+{
+    public BitmapPixelChanges PixelsChanged { get; set; }
+    public int ChangedLayerIndex { get; set; }
+
+    public BitmapChangedEventArgs(BitmapPixelChanges pixelsChanged, int changedLayerIndex)
+    {
+        PixelsChanged = pixelsChanged;
+        ChangedLayerIndex = changedLayerIndex;
+    }
+}

+ 52 - 0
PixiEditorDotNetCore3/Models/Controllers/MouseMovementController.cs

@@ -0,0 +1,52 @@
+using PixiEditorDotNetCore3.Models.Position;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows.Media;
+
+namespace PixiEditor.Models.Controllers
+{
+    public class MouseMovementController
+    {
+        public List<Coordinates> LastMouseMoveCoordinates { get; } = new List<Coordinates>();
+        public event EventHandler<MouseMovementEventArgs> MousePositionChanged;
+        public bool IsRecordingChanges { get; private set; } = false;
+
+        public void StartRecordingMouseMovementChanges()
+        {
+            if (IsRecordingChanges == false)
+            {
+                IsRecordingChanges = true;
+            }
+        }
+        public void RecordMouseMovementChanges(Coordinates mouseCoordinates)
+        {
+            if (IsRecordingChanges == true)
+            {
+                if (LastMouseMoveCoordinates.Count == 0 || mouseCoordinates != LastMouseMoveCoordinates[LastMouseMoveCoordinates.Count - 1])
+                {
+                    LastMouseMoveCoordinates.Add(mouseCoordinates);
+                    MousePositionChanged?.Invoke(this, new MouseMovementEventArgs(mouseCoordinates));
+                }
+            }
+        }
+
+        public void StopRecordingMouseMovementChanges()
+        {
+            if (IsRecordingChanges)
+            {
+                IsRecordingChanges = false;
+            }
+        }
+    }
+}
+
+public class MouseMovementEventArgs : EventArgs
+{
+    public Coordinates NewPosition { get; set; }
+
+    public MouseMovementEventArgs(Coordinates mousePosition)
+    {
+        NewPosition = mousePosition;
+    }
+}

+ 3 - 3
PixiEditorDotNetCore3/Models/Controllers/UndoManager.cs

@@ -4,7 +4,7 @@ using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
 
-namespace PixiEditorDotNetCore3.Models.Controllers
+namespace PixiEditor.Models.Controllers
 {
     public static class UndoManager
     {
@@ -46,7 +46,7 @@ namespace PixiEditorDotNetCore3.Models.Controllers
         /// <param name="oldValue">Old change value.</param>
         /// <param name="newValue">New change value.</param>
         /// <param name="undoDescription">Description of change.</param>
-        public static void RecordChanges(string property, object oldValue, string undoDescription = "")
+        public static void RecordChanges(string property, object oldValue, object newValue, string undoDescription = "")
         {
             if (_stopRecording == false)
             {
@@ -54,7 +54,7 @@ namespace PixiEditorDotNetCore3.Models.Controllers
                 {
                     _recordedChanges.RemoveAt(_recordedChanges.Count - 1);
                 }
-                _recordedChanges.Add(new Change(property, oldValue, undoDescription));
+                _recordedChanges.Add(new Change(property, oldValue, newValue, undoDescription));
 
             }
         }

+ 11 - 0
PixiEditorDotNetCore3/Models/Enums/MouseAction.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PixiEditor.Models.Enums
+{
+    public enum MouseAction
+    {
+        Move, MouseDown, MouseUp
+    }
+}

+ 5 - 5
PixiEditorDotNetCore3/Models/IO/Exporter.cs

@@ -17,8 +17,8 @@ namespace PixiEditorDotNetCore3.Models.IO
 {
     public class Exporter
     {
-        public static string _savePath = null;
-        public static Size _fileDimensions;
+        public static string SavePath = null;
+        public static Size FileDimensions;
 
         /// <summary>
         /// Creates ExportFileDialog to get width, height and path of file.
@@ -38,8 +38,8 @@ namespace PixiEditorDotNetCore3.Models.IO
                     return;
                 }
 
-                _savePath = info.FilePath;
-                _fileDimensions = new Size(info.FileWidth, info.FileHeight);
+                SavePath = info.FilePath;
+                FileDimensions = new Size(info.FileWidth, info.FileHeight);
                 SaveAsPng(info.FilePath, (int)imageToSave.Width, (int)imageToSave.Height, info.FileHeight, info.FileWidth, imageToSave);
             }
         }
@@ -53,7 +53,7 @@ namespace PixiEditorDotNetCore3.Models.IO
         {
             try
             {
-                SaveAsPng(_savePath, (int)imageToSave.Width, (int)imageToSave.Height, (int)_fileDimensions.Height, (int)_fileDimensions.Width, imageToSave);
+                SaveAsPng(SavePath, (int)imageToSave.Width, (int)imageToSave.Height, (int)FileDimensions.Height, (int)FileDimensions.Width, imageToSave);
             }
             catch (Exception ex)
             {

+ 4 - 4
PixiEditorDotNetCore3/Models/Layers/Layer.cs

@@ -42,14 +42,14 @@ namespace PixiEditorDotNetCore3.Models.Layers
             Height = (int)layerBitmap.Height;
         }
 
-        public void ApplyPixels(BitmapPixelChanges pixels, Color color)
+        public void ApplyPixels(BitmapPixelChanges pixels)
         {
             LayerBitmap.Lock();
 
-            foreach (var coords in pixels.ChangedCoordinates)
+            foreach (var coords in pixels.ChangedPixels)
             {
-                LayerBitmap.SetPixel(Math.Clamp(coords.X, 0, Width - 1), Math.Clamp(coords.Y, 0, Height - 1),
-                    color);
+                LayerBitmap.SetPixel(Math.Clamp(coords.Key.X, 0, Width - 1), Math.Clamp(coords.Key.Y, 0, Height - 1),
+                    coords.Value);
             }
 
             LayerBitmap.Unlock();

+ 34 - 19
PixiEditorDotNetCore3/Models/Position/Coordinates.cs

@@ -6,34 +6,49 @@ using System.Threading.Tasks;
 
 namespace PixiEditorDotNetCore3.Models.Position
 {
-    public class Coordinates
+    public struct Coordinates
     {
-        private int _X;
+        public int X { get; set; }
 
-        public int X
-        {
-            get { return _X; }
-            set { _X = value; }
-        }
-
-        private int _Y;
+        public int Y { get; set; }
 
-        public int Y
+        public Coordinates(int x, int y)
         {
-            get { return _Y; }
-            set { _Y = value; }
+            X = x;
+            Y = y;
         }
 
-        public Coordinates()
-        {
+        public override string ToString() => $"x: {X}, y: {Y}";
 
+        public static bool operator ==(Coordinates c1, Coordinates c2)
+        {
+            if (c1 == null || c2 == null) return false;
+            return c2.X == c1.X && c2.Y == c1.Y;
         }
 
-        public Coordinates(int x, int y)
-        {
-            X = x;
-            Y = y;
+        public static bool operator !=(Coordinates c1, Coordinates c2)
+        {
+            return !(c1 == c2);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (obj.GetType() != typeof(Coordinates)) return false;
+            return this == (Coordinates)obj;
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                const int HashingBase = (int)2166136261;
+                const int HashingMultiplier = 16777619;
+
+                int hash = HashingBase;
+                hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, X) ? X.GetHashCode() : 0);
+                hash = (hash * HashingMultiplier) ^ (!ReferenceEquals(null, Y) ? Y.GetHashCode() : 0);            
+                return hash;
+            }
         }
-
     }
 }

+ 13 - 5
PixiEditorDotNetCore3/Models/Tools/BitmapPixelChanges.cs

@@ -8,13 +8,21 @@ namespace PixiEditorDotNetCore3.Models.Tools
 {
     public struct BitmapPixelChanges
     {
-        public Coordinates[] ChangedCoordinates { get; set; }
-        public Color PixelsColor { get; set; }
+        public Dictionary<Coordinates, Color> ChangedPixels;
 
-        public BitmapPixelChanges(Coordinates[] changedCoordinates, Color color)
+        public BitmapPixelChanges(Dictionary<Coordinates, Color> changedPixels)
         {
-            ChangedCoordinates = changedCoordinates;
-            PixelsColor = color;
+            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);
         }
     }
 }

+ 1 - 1
PixiEditorDotNetCore3/Models/Tools/ShapeTool.cs

@@ -16,7 +16,7 @@ namespace PixiEditorDotNetCore3.Models.Tools
             ExecutesItself = true;
         }
 
-        public abstract override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize);
+        public abstract override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize);
 
         protected DoubleCords CalculateCoordinatesForShapeRotation(Coordinates startingCords)
         {

+ 1 - 1
PixiEditorDotNetCore3/Models/Tools/Tool.cs

@@ -9,7 +9,7 @@ namespace PixiEditorDotNetCore3.Models.Tools
 {
     public abstract class Tool
     {
-        public abstract BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize);
+        public abstract BitmapPixelChanges Use(Layer layer, Coordinates[] pixels, Color color, int toolSize);
         public abstract ToolType ToolType { get; }
         public bool ExecutesItself = false;
     }

+ 3 - 3
PixiEditorDotNetCore3/Models/Tools/Tools/BrightnessTool.cs

@@ -13,13 +13,13 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
         public const float DarkenFactor = -0.06f;
         public const float LightenFactor = 0.1f;
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
             if(Mouse.LeftButton == MouseButtonState.Pressed)
             {
-                return ChangeBrightness(layer, startingCoords, toolSize, LightenFactor);
+                return ChangeBrightness(layer, coordinates[0], toolSize, LightenFactor);
             }
-                return ChangeBrightness(layer, startingCoords, toolSize, DarkenFactor);
+                return ChangeBrightness(layer, coordinates[0], toolSize, DarkenFactor);
         }       
 
         private BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)

+ 2 - 2
PixiEditorDotNetCore3/Models/Tools/Tools/CircleTool.cs

@@ -9,9 +9,9 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
     {
         public override ToolType ToolType => ToolType.Circle;
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
-            CreateCircle(layer, startingCoords, color, toolSize);
+            CreateCircle(layer, coordinates[0], color, toolSize);
             return new BitmapPixelChanges();
         }
 

+ 2 - 2
PixiEditorDotNetCore3/Models/Tools/Tools/EarserTool.cs

@@ -11,10 +11,10 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
     {
         public override ToolType ToolType => ToolType.Earser;
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
             PenTool pen = new PenTool();
-            return pen.Draw(startingCoords, System.Windows.Media.Colors.Transparent, toolSize);
+            return pen.Draw(coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
         }
     }
 }

+ 3 - 3
PixiEditorDotNetCore3/Models/Tools/Tools/FloodFill.cs

@@ -16,9 +16,9 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
             ExecutesItself = true;
         }
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
-            return ForestFire(layer, startingCoords, color);
+            return ForestFire(layer, coordinates[0], color);
         }
 
         public BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
@@ -47,7 +47,7 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
                     stack.Push(Tuple.Create(point.Item1 - 1, point.Item2));
                 }
             }
-            return new BitmapPixelChanges(changedCoords.ToArray(), newColor);
+            return BitmapPixelChanges.FromSingleColoredArray(changedCoords.ToArray(), newColor);
         }
     }
 }

+ 2 - 2
PixiEditorDotNetCore3/Models/Tools/Tools/LineTool.cs

@@ -9,9 +9,9 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
     {
         public override ToolType ToolType => ToolType.Line;
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
-            CreateLine(layer, startingCoords, color, toolSize);
+            CreateLine(layer, coordinates[0], color, toolSize);
             return new BitmapPixelChanges();
         }
 

+ 3 - 3
PixiEditorDotNetCore3/Models/Tools/Tools/PenTool.cs

@@ -12,9 +12,9 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
         public override ToolType ToolType => ToolType.Pen;
 
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
-            return Draw(startingCoords, color, toolSize);
+            return Draw(coordinates[0], color, toolSize);
         }
 
         public BitmapPixelChanges Draw(Coordinates startingCoords, Color color, int toolSize)
@@ -25,7 +25,7 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
             y1 = centeredCoords.Coords1.Y;
             x2 = centeredCoords.Coords2.X;
             y2 = centeredCoords.Coords2.Y;
-            return new BitmapPixelChanges(CoordinatesCalculator.RectangleToCoordinates(x1, y1, x2, y2), color);
+            return BitmapPixelChanges.FromSingleColoredArray(CoordinatesCalculator.RectangleToCoordinates(x1, y1, x2, y2), color);
         }
     }
 }

+ 2 - 2
PixiEditorDotNetCore3/Models/Tools/Tools/RectangleTool.cs

@@ -9,9 +9,9 @@ namespace PixiEditorDotNetCore3.Models.Tools.Tools
     {
         public override ToolType ToolType => ToolType.Rectangle;
 
-        public override BitmapPixelChanges Use(Layer layer, Coordinates startingCoords, Color color, int toolSize)
+        public override BitmapPixelChanges Use(Layer layer, Coordinates[] coordinates, Color color, int toolSize)
         {
-            CreateRectangle(layer,startingCoords,color,toolSize);
+            CreateRectangle(layer,coordinates[0] ,color,toolSize);
             return new BitmapPixelChanges();
         }
 

+ 2 - 2
PixiEditorDotNetCore3/Models/Tools/ToolsManager.cs

@@ -50,11 +50,11 @@ namespace PixiEditorDotNetCore3.Models.Tools
                     RestoreLastBitmap();
                 }
 
-                BitmapPixelChanges changes = SelectedTool.Use(_layer, _startCoordinates, _color, _toolSzie);
+                BitmapPixelChanges changes = SelectedTool.Use(_layer, new[] { _startCoordinates }, _color, _toolSzie);
 
                 if (!SelectedTool.ExecutesItself)
                 {
-                    _layer.ApplyPixels(changes, changes.PixelsColor);
+                    _layer.ApplyPixels(changes);
                 }
                 _layer.LayerBitmap.Unlock();
 

+ 2 - 0
PixiEditorDotNetCore3/PixiEditorDotNetCore3.csproj → PixiEditorDotNetCore3/PixiEditor.csproj

@@ -7,6 +7,8 @@
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
+    <AssemblyName>PixiEditor</AssemblyName>
+    <RootNamespace>PixiEditor</RootNamespace>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Expression.Blend.Sdk">

+ 2 - 2
PixiEditorDotNetCore3/PixiEditorDotNetCore3.sln → PixiEditorDotNetCore3/PixiEditor.sln

@@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio Version 16
 VisualStudioVersion = 16.0.28729.10
 MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditorDotNetCore3", "PixiEditorDotNetCore3.csproj", "{B17A8190-059F-4FDD-A1B5-4F63D866778F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor", "PixiEditor.csproj", "{B17A8190-059F-4FDD-A1B5-4F63D866778F}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditorTests", "..\PixiEditorTests\PixiEditorTests.csproj", "{D61922EA-3BF3-4AFA-8930-3A8B30A9A195}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditorTests", "..\PixiEditorTests\PixiEditorTests.csproj", "{D61922EA-3BF3-4AFA-8930-3A8B30A9A195}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution

+ 83 - 150
PixiEditorDotNetCore3/ViewModels/ViewModelMain.cs

@@ -12,26 +12,19 @@ using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using PixiTools = PixiEditorDotNetCore3.Models.Tools.Tools;
-using PixiEditorDotNetCore3.Models.Controllers;
+using PixiEditor.Models.Controllers;
 using PixiEditorDotNetCore3.Models.Dialogs;
 using PixiEditorDotNetCore3.Models.Images;
 using PixiEditorDotNetCore3.Models.IO;
 using PixiEditorDotNetCore3.Models.Layers;
 using PixiEditorDotNetCore3.Models.Position;
+using PixiEditor.Models.Enums;
 
 namespace PixiEditor.ViewModels
 {
     class ViewModelMain : ViewModelBase
     {
 
-        private ObservableCollection<Layer> _layers;
-
-        public ObservableCollection<Layer> Layers
-        {
-            get => _layers;
-            set { if (_layers != value) { _layers = value;} }
-        }
-
         public RelayCommand SelectToolCommand { get; set; } //Command that handles tool switching 
         public RelayCommand GenerateDrawAreaCommand { get; set; } //Command that generates draw area
         public RelayCommand MouseMoveOrClickCommand { get; set; } //Command that is used to draw
@@ -57,37 +50,12 @@ namespace PixiEditor.ViewModels
             }
         }
 
-        private Layer _activeLayer;
-
-        public Layer ActiveLayer
-        {
-            get => _activeLayer;
-            set {
-                _activeLayer = value;
-                ReloadImage();
-                RaisePropertyChanged("ActiveLayer");
-            }
-        }
-
-        public LightLayer ActiveLightLayer
-        {
-            get
-            {
-                if (_activeLayer != null)
-                    return new LightLayer(
-                        _activeLayer.ConvertBitmapToBytes(), 
-                        ActiveLayer.Height, ActiveLayer.Width);
-                return null;
-            }
-            set => ActiveLayer = new Layer(BitmapConverter.BytesToWriteableBitmap(ActiveLayer.Width, ActiveLayer.Height,value.LayerBytes));
-        }
-
         private double _mouseXonCanvas;
 
         public double MouseXOnCanvas //Mouse X coordinate relative to canvas
         {
             get => _mouseXonCanvas;
-            set { _mouseXonCanvas = value;  RaisePropertyChanged("MouseXonCanvas"); }
+            set { _mouseXonCanvas = value; RaisePropertyChanged("MouseXonCanvas"); }
         }
 
         private double _mouseYonCanvas;
@@ -107,8 +75,9 @@ namespace PixiEditor.ViewModels
             set
             {
                 if (_primaryColor != value)
-                {
-                   _primaryColor = value;
+                {
+                    _primaryColor = value;
+                    BitmapUtility.PrimaryColor = value;
                     RaisePropertyChanged("PrimaryColor");
                 }
             }
@@ -120,45 +89,48 @@ namespace PixiEditor.ViewModels
         {
             get => _secondaryColor;
             set { if (_secondaryColor != value) { _secondaryColor = value; RaisePropertyChanged("SecondaryColor"); } }
-        }
-
-        private Color _selectedColor = Colors.White;
-
-        public Color SelectedColor
-        {
-            get { return _selectedColor; }
-            set 
-            { 
-                if(_selectedColor != value) 
-                    _selectedColor = value;
-                RaisePropertyChanged("SelectedColor");
-            }
         }
 
-        private ToolType _selectedTool = ToolType.Pen;
+        private ToolType _selectedTool;
 
         public ToolType SelectedTool
         {
             get { return _selectedTool; }
-            set { if (_selectedTool != value) { _selectedTool = value; primaryToolSet.SetTool(SelectedTool); RaisePropertyChanged("SelectedTool"); } }
-        }
+            set 
+            { 
+                if (_selectedTool != value) 
+                { 
+                    _selectedTool = value; 
+                    SetActiveTool(value); 
+                    RaisePropertyChanged("SelectedTool"); } }
+        }        
 
 
-        private int _toolSize = 1;
+        private int _toolSize;
 
         public int ToolSize
         {
             get { return _toolSize; }
-            set { if (_toolSize != value) { _toolSize = value; RaisePropertyChanged("ToolSize"); } }
+            set 
+            { 
+                if (_toolSize != value) 
+                { 
+                    _toolSize = value;
+                    BitmapUtility.ToolSize = value;
+                    RaisePropertyChanged("ToolSize"); 
+                } 
+            }
         }
 
         private ToolsManager primaryToolSet;
-        private WriteableBitmap _lastBlendedBitmap;
+
+        public BitmapOperationsUtility BitmapUtility { get; set; }
 
         public ViewModelMain()
         {
             PixiFilesManager.InitializeTempDirectories();
-            Layers = new ObservableCollection<Layer>();
+            BitmapUtility = new BitmapOperationsUtility();
+            BitmapUtility.BitmapChanged += BitmapUtility_BitmapChanged;
             SelectToolCommand = new RelayCommand(RecognizeTool);
             GenerateDrawAreaCommand = new RelayCommand(GenerateDrawArea);
             MouseMoveOrClickCommand = new RelayCommand(MouseMoveOrClick);
@@ -170,33 +142,24 @@ namespace PixiEditor.ViewModels
             OpenFileCommand = new RelayCommand(OpenFile);
             SetActiveLayerCommand = new RelayCommand(SetActiveLayer);
             NewLayerCommand = new RelayCommand(NewLayer, CanCreateNewLayer);
-            ReloadImageCommand = new RelayCommand(ReloadImage);
             primaryToolSet = new ToolsManager(new List<Tool> { new PixiTools.PenTool(), new PixiTools.FloodFill(), new PixiTools.LineTool(),
             new PixiTools.CircleTool(), new PixiTools.RectangleTool(), new PixiTools.EarserTool(), new PixiTools.BrightnessTool()});
             UndoManager.SetMainRoot(this);
-            primaryToolSet.SetTool(SelectedTool);
-        }
-
-        public WriteableBitmap BlendLayersBitmaps()
-        {
-            Rect size = new Rect(new Size(ActiveLayer.Width, ActiveLayer.Height));
-            Layer[] visibleLayers = Layers.Where(x => x.IsVisible).ToArray();
-            if (visibleLayers.Length == 0)
-            {
-                return BitmapFactory.New(0,0);
-            }
-
-            WriteableBitmap bitmap = visibleLayers[0].LayerBitmap.Clone();
-
-            for (int i = 1; i < visibleLayers.Length; i++)
-            {
-                bitmap.Blit(size, visibleLayers[i].LayerBitmap,
-                    size, WriteableBitmapExtensions.BlendMode.Additive);
-            }
-
-            return bitmap;
-        }
-
+            SetActiveTool(ToolType.Pen);
+            BitmapUtility.PrimaryColor = PrimaryColor;
+            ToolSize = 1;
+        }
+
+        public void SetActiveLayer(object parameter)
+        {
+            BitmapUtility.SetActiveLayer((int)parameter);
+        }
+
+        private void BitmapUtility_BitmapChanged(object sender, BitmapChangedEventArgs e)
+        {
+            RefreshImage();
+        }
+
         #region Undo/Redo
         /// <summary>
         /// Undo last action
@@ -243,6 +206,12 @@ namespace PixiEditor.ViewModels
             ToolType tool = (ToolType)Enum.Parse(typeof(ToolType), parameter.ToString());
             SelectedTool = tool;
         }
+
+        private void SetActiveTool(ToolType tool)
+        {
+            primaryToolSet.SetTool(tool);
+            BitmapUtility.SelectedTool = primaryToolSet.SelectedTool;
+        }
         /// <summary>
         /// When mouse is up stops recording changes.
         /// </summary>
@@ -250,6 +219,7 @@ namespace PixiEditor.ViewModels
         private void MouseUp(object parameter)
         {
             UndoManager.StopRecording();
+            BitmapUtility.MouseController.StopRecordingMouseMovementChanges();
             primaryToolSet.StopExectuingTool();
         }
 
@@ -260,67 +230,38 @@ namespace PixiEditor.ViewModels
         private void MouseMoveOrClick(object parameter)
         {
             Coordinates cords = new Coordinates((int)MouseXOnCanvas, (int)MouseYOnCanvas);
-            MousePositionConverter.CurrentCoordinates = cords;
-
-            if (Mouse.LeftButton == MouseButtonState.Pressed)
-            {
-                SelectedColor = PrimaryColor;
-            }
-            else if(Mouse.RightButton == MouseButtonState.Pressed)
-            {
-                SelectedColor = SecondaryColor;
-            }
-            else
-            {
-                return;
-            }
-
-            if (SelectedTool != ToolType.ColorPicker)
+            MousePositionConverter.CurrentCoordinates = cords;
+
+            if ((Models.Enums.MouseAction)parameter == Models.Enums.MouseAction.MouseDown)
             {
-                UndoManager.RecordChanges("ActiveLightLayer", new LightLayer(ActiveLayer.ConvertBitmapToBytes(), (int)ActiveLayer.LayerBitmap.Height, 
-                    (int)ActiveLayer.LayerBitmap.Width), $"Used {SelectedTool.ToString()}");
-                primaryToolSet.ExecuteTool(ActiveLayer, cords, SelectedColor, ToolSize);
-                RefreshImage();
+                if (!BitmapUtility.MouseController.IsRecordingChanges)
+                {
+                    BitmapUtility.MouseController.StartRecordingMouseMovementChanges();
+                }
             }
-            else
+            if((Models.Enums.MouseAction)parameter == Models.Enums.MouseAction.Move)
             {
-                ExecuteColorPicker(cords);
+                if (BitmapUtility.MouseController.IsRecordingChanges)
+                {
+                    BitmapUtility.MouseController.RecordMouseMovementChanges(cords);
+                }
             }
-        }
-
-
+        }
+
+
+
         private void ExecuteColorPicker(Coordinates cords)
         {
             if (Mouse.LeftButton == MouseButtonState.Pressed)
             {
-                PrimaryColor = ActiveLayer.LayerBitmap.GetPixel(cords.X, cords.Y);
+                PrimaryColor = BitmapUtility.ActiveLayer.LayerBitmap.GetPixel(cords.X, cords.Y);
             }
             else
             {
-                SecondaryColor = ActiveLayer.LayerBitmap.GetPixel(cords.X, cords.Y);
+                SecondaryColor = BitmapUtility.ActiveLayer.LayerBitmap.GetPixel(cords.X, cords.Y);
             }
         }
 
-        private void ReloadImage(object property=null)
-        {
-            if (ActiveLayer != null)
-            {
-                _lastBlendedBitmap = BlendLayersBitmaps();
-                ActiveImage.Source = _lastBlendedBitmap;
-            }
-        }
-
-        private void RefreshImage()
-        {
-            if (ActiveLayer != null)
-            {
-                Rect size = new Rect(new Size(ActiveLayer.Height, ActiveLayer.Width));
-                 _lastBlendedBitmap.Blit(size, ActiveLayer.LayerBitmap,
-                    size);
-                ActiveImage.Source = _lastBlendedBitmap;
-            }
-        }
-
         /// <summary>
         /// Generates new Layer and sets it as active one
         /// </summary>
@@ -330,10 +271,9 @@ namespace PixiEditor.ViewModels
             NewFileDialog newFile = new NewFileDialog();
             if (newFile.ShowDialog())
             {
-                Layers.Clear();
-                Layers.Add(new Layer("Base Layer",newFile.Width, newFile.Height));
+                BitmapUtility.Layers.Clear();
+                BitmapUtility.AddNewLayer("Base Layer", newFile.Width, newFile.Height, true);
                 ActiveImage = ImageGenerator.GenerateForPixelArts(newFile.Width, newFile.Height);
-                ActiveLayer = Layers[0];
             }
         }
         #region SaveFile
@@ -343,10 +283,9 @@ namespace PixiEditor.ViewModels
         /// <param name="parameter"></param>
         private void SaveFile(object parameter)
         {
-            ReloadImage();
-            if (Exporter._savePath == null)
+            if (Exporter.SavePath == null)
             {
-                Exporter.Export(FileType.PNG, ActiveImage, new Size(ActiveLayer.Width, ActiveLayer.Height));
+                Exporter.Export(FileType.PNG, ActiveImage, new Size(BitmapUtility.ActiveLayer.Width, BitmapUtility.ActiveLayer.Height));
             }
             else
             {
@@ -360,7 +299,7 @@ namespace PixiEditor.ViewModels
         /// <returns></returns>
         private bool CanSave(object property)
         {
-            return ActiveLayer != null;
+            return BitmapUtility.ActiveLayer != null;
         }
         #endregion
 
@@ -373,12 +312,10 @@ namespace PixiEditor.ViewModels
             ImportFileDialog dialog = new ImportFileDialog();
             if (dialog.ShowDialog())
             {
-                Layers.Clear();
-                Layers.Add(new Layer("Base Layer",dialog.FileWidth, dialog.FileHeight));
+                BitmapUtility.Layers.Clear();
+                BitmapUtility.AddNewLayer("Base Layer", dialog.FileWidth, dialog.FileHeight, true);
                 ActiveImage = ImageGenerator.GenerateForPixelArts(dialog.FileWidth, dialog.FileHeight);
-                SetActiveLayer(0);
-                ActiveLayer.LayerBitmap = Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight);
-                ReloadImage();
+                BitmapUtility.ActiveLayer.LayerBitmap = Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight);
             }
         }
 
@@ -391,23 +328,19 @@ namespace PixiEditor.ViewModels
             MessageBox.Show("This feature is not implemented yet.", "Feature not implemented", MessageBoxButton.OK, MessageBoxImage.Information);
         }
 
-        public void SetActiveLayer(object parameter)
-        {
-            UndoManager.AddUndoChange("ActiveLayer", ActiveLayer, $"Changed layer to {Layers[(int)parameter].Name}");
-            ActiveLayer = Layers[(int) parameter];
-            ReloadImage();
+        public void RefreshImage()
+        {
+            ActiveImage.Source = BitmapUtility.ActiveLayer.LayerBitmap;
         }
 
         public void NewLayer(object parameter)
         {
-            Layers.Add(new Layer("New Layer", Layers[0].Width, Layers[0].Height));
-            Layers.Move(Layers.Count - 1, 0);
-            ActiveLayer = Layers[0];
+            BitmapUtility.AddNewLayer("New Layer", BitmapUtility.Layers[0].Width, BitmapUtility.Layers[0].Height);
         }
 
         public bool CanCreateNewLayer(object parameter)
-        {
-            return Layers.Count > 0;
+        {
+                return BitmapUtility.Layers.Count > 0;
         }
     }
 }

+ 7 - 6
PixiEditorDotNetCore3/Views/MainWindow.xaml

@@ -5,6 +5,7 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vws="clr-namespace:PixiEditor.Views"
+        xmlns:enums="clr-namespace:PixiEditor.Models.Enums"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
@@ -111,22 +112,22 @@
             <Grid>
                 <vws:MainDrawingPanel CenterOnStart="True">
                     <vws:MainDrawingPanel.Item>
-                        <Canvas Width="{Binding ActiveLayer.Width}" Height="{Binding ActiveLayer.Height}" VerticalAlignment="Center" HorizontalAlignment="Center">
+                        <Canvas Width="{Binding BitmapUtility.ActiveLayer.Width}" Height="{Binding BitmapUtility.ActiveLayer.Height}" VerticalAlignment="Center" HorizontalAlignment="Center">
                             <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="MouseMove">
-                                    <i:InvokeCommandAction Command="{Binding MouseMoveOrClickCommand}"/>
+                                    <i:InvokeCommandAction Command="{Binding MouseMoveOrClickCommand}" CommandParameter="{x:Static enums:MouseAction.Move}"/>
                                 </i:EventTrigger>
                                 <i:EventTrigger EventName="MouseDown">
-                                    <i:InvokeCommandAction Command="{Binding MouseMoveOrClickCommand}"/>
+                                    <i:InvokeCommandAction Command="{Binding MouseMoveOrClickCommand}" CommandParameter="{x:Static enums:MouseAction.MouseDown}"/>
                                 </i:EventTrigger>
                                 <i:EventTrigger EventName="MouseUp">
-                                    <i:InvokeCommandAction Command="{Binding MouseUpCommand}"/>
+                                    <i:InvokeCommandAction Command="{Binding MouseUpCommand}" CommandParameter="{x:Static enums:MouseAction.MouseUp}"/>
                                 </i:EventTrigger>
                             </i:Interaction.Triggers>
                             <i:Interaction.Behaviors>
                                 <behaviors:MouseBehaviour MouseX="{Binding MouseXOnCanvas, Mode=OneWayToSource}" MouseY="{Binding MouseYOnCanvas, Mode=OneWayToSource}"/>
                             </i:Interaction.Behaviors>
-                            <Image Source="/Images/transparentbg.png" Height="{Binding ActiveLayer.Height}" Width="{Binding ActiveLayer.Width}" Opacity="0.2" Stretch="UniformToFill"/>
+                            <Image Source="/Images/transparentbg.png" Height="{Binding BitmapUtility.ActiveLayer.Height}" Width="{Binding BitmapUtility.ActiveLayer.Width}" Opacity="0.2" Stretch="UniformToFill"/>
                             <ContentControl Content="{Binding ActiveImage}"/>
                         </Canvas>
                     </vws:MainDrawingPanel.Item>
@@ -210,7 +211,7 @@
                         <xcad:LayoutAnchorable ContentId="layers" Title="Layers" CanHide="False" CanClose="False" CanAutoHide="False" CanDockAsTabbedDocument="False" CanFloat="True">
                             <StackPanel  Orientation="Vertical">
                                 <Button Command="{Binding NewLayerCommand}" Height="30" Content="New Layer" HorizontalAlignment="Stretch" Margin="5" Style="{StaticResource DarkRoundButton}"/>
-                                <ItemsControl ItemsSource="{Binding Layers}" AlternationCount="9999">
+                                <ItemsControl ItemsSource="{Binding BitmapUtility.Layers}" AlternationCount="9999">
                                     <ItemsControl.ItemTemplate>
                                         <DataTemplate>
                                             <Border BorderBrush="Gray" BorderThickness="1">

+ 3 - 3
PixiEditorTests/PixiEditorTests.csproj

@@ -17,11 +17,11 @@
   </ItemGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\PixiEditorDotNetCore3\PixiEditorDotNetCore3.csproj" />
+    <Folder Include="ViewModelsTests\" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="ViewModelsTests\" />
+  <ItemGroup>
+    <ProjectReference Include="..\PixiEditorDotNetCore3\PixiEditor.csproj" />
   </ItemGroup>
 
 </Project>