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;
}
}
}