using PixiEditor.Helpers; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Media; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Imaging; namespace PixiEditor.Models.Tools { public class ToolSet { private Coordinates _activeCoordinates = new Coordinates(); private bool _toolIsExecuting = false; private int _asyncDelay = 15; private WriteableBitmap _oldBitmap; /// /// Executes tool action /// /// Layer to operate on. /// Click coordinates. /// Color that tool will use. /// Size/thickness of tool /// Tool to execute /// public Layer ExecuteTool(Layer layer, Coordinates startingCoords, Color color,int toolSize, ToolType tool) { if (toolSize < 1) return null; Layer cLayer = layer; _oldBitmap = new WriteableBitmap(layer.LayerBitmap); switch (tool) { case ToolType.Pen: cLayer.LayerBitmap = DrawPixel(cLayer.LayerBitmap, startingCoords, toolSize,color); break; case ToolType.Bucket: cLayer.LayerBitmap = FloodFill(cLayer.LayerBitmap, startingCoords, color); break; case ToolType.Line: if (_toolIsExecuting == false) { LineAsync(cLayer, startingCoords, color, toolSize); } break; case ToolType.Circle: if(_toolIsExecuting == false) { CircleAsync(cLayer, startingCoords, color); } break; case ToolType.Rectangle: if(_toolIsExecuting == false) { RectangleAsync(cLayer, startingCoords, color); } break; case ToolType.Earser: cLayer.LayerBitmap = DrawPixel(cLayer.LayerBitmap, startingCoords, toolSize, Colors.Transparent); break; case ToolType.Lighten: if(Mouse.LeftButton == MouseButtonState.Pressed) { cLayer.LayerBitmap = Lighten(cLayer.LayerBitmap, startingCoords); } else if(Mouse.RightButton == MouseButtonState.Pressed) { cLayer.LayerBitmap = Darken(cLayer.LayerBitmap, startingCoords); } break; } if (tool != ToolType.ColorPicker) { UndoManager.RecordChanges("ActiveLightLayer", new LightLayer(_oldBitmap.ToByteArray(), (int)_oldBitmap.Height, (int)_oldBitmap.Width), $"{tool.ToString()} Tool."); } return cLayer; } /// /// Not working yet. /// /// /// /// /// public static void HighlightPixel(WriteableBitmap bitmap,Coordinates pixelCoordinates, Color color, int highlightThickness) { bitmap.Clear(); bitmap.Blit(new Rect(new Size(bitmap.Width, bitmap.Height)), bitmap, new Rect(new Size(bitmap.Width, bitmap.Height)), WriteableBitmapExtensions.BlendMode.Additive); DCords centerCords = CalculateThicknessCenter(pixelCoordinates, highlightThickness); bitmap.FillRectangle(centerCords.Coords1.X, centerCords.Coords1.Y, centerCords.Coords2.X, centerCords.Coords2.Y, color); } /// /// Updates coordinates in order to some tools work /// /// Current coordinates public void UpdateCoordinates(Coordinates cords) { _activeCoordinates = cords; } /// /// Fills pixel(s) with choosen color /// /// Bitmap to operate on. /// Coordinates of pixel. /// Color to be set. private WriteableBitmap DrawPixel(WriteableBitmap canvas, Coordinates pixelPosition,int thickness,Color color) { WriteableBitmap bm = canvas; int x1, y1, x2, y2; DCords centeredCoords = CalculateThicknessCenter(pixelPosition, thickness); x1 = centeredCoords.Coords1.X; y1 = centeredCoords.Coords1.Y; x2 = centeredCoords.Coords2.X; y2 = centeredCoords.Coords2.Y; bm.FillRectangle(x1, y1, x2, y2, color); return bm; } /// /// Calculates center of thickness * thickness rectangle /// /// Top left position of rectangle /// Thickness of rectangle /// private static DCords 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 DCords(new Coordinates(x1, y1), new Coordinates(x2, y2)); } /// /// 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; } } }