using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Input; using System.Windows.Media.Imaging; namespace PixiEditorDotNetCore3.Models.Tools { public class ToolSet { public List Tools { get; set; } = new List(); private Coordinates _activeCoordinates = new Coordinates(); private bool _toolIsExecuting; private int _asyncDelay = 15; public ToolSet(List tools) { Tools = tools; } /// /// Executes tool action /// /// Layer to operate on. /// Click coordinates. /// Color that tool will use. /// Size/thickness of tool /// Tool to execute /// public void ExecuteTool(Layer layer, Coordinates startingCoords, Color color,int toolSize, ToolType tool) { if (toolSize < 1) return; BitmapPixelChanges changes; Tool selectedTool = Tools.Find(x => x.GetToolType == tool); changes = selectedTool.Use(layer, startingCoords, color, toolSize); if (tool != ToolType.ColorPicker) { UndoManager.RecordChanges("ActiveLightLayer", new LightLayer(layer.LayerBitmap.ToByteArray(), (int)layer.LayerBitmap.Height, (int)layer.LayerBitmap.Width), $"{tool.ToString()} Tool."); } if (selectedTool.ExecutesItself == false) { layer.ApplyPixels(changes, color); } } /// /// Updates coordinates in order to some tools work /// /// Current coordinates public void UpdateCoordinates(Coordinates cords) { _activeCoordinates = cords; } /// /// Fills area with color (forest fire alghoritm) /// /// Bitmap to operate on /// Position of starting pixel /// Fills area with this color private WriteableBitmap FloodFill(WriteableBitmap canvas, Coordinates pixelPosition, Color color) { WriteableBitmap bm = canvas; Color colorToReplace = bm.GetPixel(pixelPosition.X, pixelPosition.Y); var stack = new Stack>(); stack.Push(Tuple.Create(pixelPosition.X, pixelPosition.Y)); while (stack.Count > 0) { var point = stack.Pop(); if (point.Item1 < 0 || point.Item1 > bm.Height - 1) continue; if (point.Item2 < 0 || point.Item2 > bm.Width - 1) continue; if (bm.GetPixel(point.Item1, point.Item2) == color) continue; if (bm.GetPixel(point.Item1, point.Item2) == colorToReplace) { bm.SetPixel(point.Item1, point.Item2, color); stack.Push(Tuple.Create(point.Item1, point.Item2 - 1)); stack.Push(Tuple.Create(point.Item1 + 1, point.Item2)); stack.Push(Tuple.Create(point.Item1, point.Item2 + 1)); stack.Push(Tuple.Create(point.Item1 - 1, point.Item2)); } } return bm; } /// /// Draws line in canvas /// /// Layer to operate on /// Starting coordinates, usually click point /// Does it really need a description? private async void LineAsync(Layer layer, Coordinates coordinates, Color color, int size) { WriteableBitmap wb = layer.LayerBitmap; _toolIsExecuting = true; //clones bitmap before line WriteableBitmap writeableBitmap = wb.Clone(); //While Mouse buttons are pressed, clears current bitmap, pastes cloned bitmap and draws line, on each iteration while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed) { wb.Clear(); wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive); wb.DrawLineBresenham(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color); await Task.Delay(_asyncDelay); } _toolIsExecuting = false; } /// /// Draws circle on bitmap. /// /// Layer to operate on. /// Starting pixel coordinates. /// Circle color. private async void CircleAsync(Layer layer, Coordinates coordinates, Color color) { WriteableBitmap wb = layer.LayerBitmap; //Basically does the same like rectangle method, but with different shape _toolIsExecuting = true; WriteableBitmap bitmap = wb.Clone(); while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed) { wb.Clear(); wb.Blit(new Rect(new Size(layer.Width, layer.Height)), bitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive); if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y) { wb.DrawEllipse(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color); } else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y) { wb.DrawEllipse(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color); } else if (coordinates.Y > _activeCoordinates.Y) { wb.DrawEllipse(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color); } else { wb.DrawEllipse(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color); } await Task.Delay(_asyncDelay); } _toolIsExecuting = false; } /// /// Draws rectangle on bitmap /// /// Layer to operate on /// Starting pixel coordinate /// Rectangle color private async void RectangleAsync(Layer layer, Coordinates coordinates, Color color) { WriteableBitmap wb = layer.LayerBitmap; _toolIsExecuting = true; WriteableBitmap writeableBitmap = wb.Clone(); while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed) { //Two lines below are responsible for clearing last rectangle (on mouse move), to live show rectangle on bitmap wb.Clear(); wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive); //Those ifs are changing direction of rectangle. In other words: flips rectangle on X and Y axis when needed if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y) { wb.DrawRectangle(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color); } else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y) { wb.DrawRectangle(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color); } else if (coordinates.Y > _activeCoordinates.Y) { wb.DrawRectangle(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color); } else { wb.DrawRectangle(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color); } await Task.Delay(_asyncDelay); } _toolIsExecuting = false; } /// /// Returns color of pixel. /// /// Layer in which bitmap with pixels are stored. /// Pixel coordinate. /// public static Color ColorPicker(Layer layer, Coordinates coordinates) { return layer.LayerBitmap.GetPixel(coordinates.X, coordinates.Y); } /// /// Ligtens pixel color. /// /// Bitmap to work on. /// Pixel coordinates. /// private WriteableBitmap Lighten(WriteableBitmap bitmap, Coordinates coordinates) { WriteableBitmap wb = bitmap; Color pixel = wb.GetPixel(coordinates.X, coordinates.Y); Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R, pixel.G, pixel.B), 0.1f); wb.SetPixel(coordinates.X, coordinates.Y, newColor); return wb; } /// /// Darkens pixel color. /// /// Bitmap to work on. /// Pixel coordinates. /// private WriteableBitmap Darken(WriteableBitmap bitmap, Coordinates coordinates) { WriteableBitmap wb = bitmap; Color pixel = wb.GetPixel(coordinates.X, coordinates.Y); Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R,pixel.G,pixel.B), -0.06f); wb.SetPixel(coordinates.X, coordinates.Y, newColor); return wb; } } }