ToolSet.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. using PixiEditor.Helpers;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using System.Windows.Media;
  10. using System.Windows.Controls;
  11. using System.Windows.Input;
  12. using System.Windows.Media.Imaging;
  13. namespace PixiEditorDotNetCore3.Models.Tools
  14. {
  15. public class ToolSet
  16. {
  17. public List<Tool> Tools { get; set; } = new List<Tool>();
  18. private Coordinates _activeCoordinates = new Coordinates();
  19. private bool _toolIsExecuting = false;
  20. private int _asyncDelay = 15;
  21. public ToolSet(List<Tool> tools)
  22. {
  23. Tools = tools;
  24. }
  25. /// <summary>
  26. /// Executes tool action
  27. /// </summary>
  28. /// <param name="layer">Layer to operate on.</param>
  29. /// <param name="startingCoords">Click coordinates.</param>
  30. /// <param name="color">Color that tool will use.</param>
  31. /// <param name="toolSize">Size/thickness of tool</param>
  32. /// <param name="tool">Tool to execute</param>
  33. /// <returns></returns>
  34. public void ExecuteTool(Layer layer, Coordinates startingCoords, Color color,int toolSize, ToolType tool)
  35. {
  36. if (toolSize < 1) return;
  37. BitmapPixelChanges changes;
  38. Tool selectedTool = Tools.Find(x => x.GetToolType() == tool);
  39. changes = selectedTool.Use(layer, startingCoords, color, toolSize);
  40. if (tool != ToolType.ColorPicker)
  41. {
  42. UndoManager.RecordChanges("ActiveLightLayer", new LightLayer(layer.LayerBitmap.ToByteArray(), (int)layer.LayerBitmap.Height, (int)layer.LayerBitmap.Width),
  43. $"{tool.ToString()} Tool.");
  44. }
  45. layer.ApplyPixels(changes, color);
  46. }
  47. /// <summary>
  48. /// Not working yet.
  49. /// </summary>
  50. /// <param name="bitmap"></param>
  51. /// <param name="pixelCoordinates"></param>
  52. /// <param name="color"></param>
  53. /// <param name="highlightThickness"></param>
  54. public static void HighlightPixel(WriteableBitmap bitmap,Coordinates pixelCoordinates, Color color, int highlightThickness)
  55. {
  56. bitmap.Clear();
  57. bitmap.Blit(new Rect(new Size(bitmap.Width, bitmap.Height)), bitmap, new Rect(new Size(bitmap.Width, bitmap.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  58. DoubleCords centerCords = CoordinatesCalculator.CalculateThicknessCenter(pixelCoordinates, highlightThickness);
  59. bitmap.FillRectangle(centerCords.Coords1.X, centerCords.Coords1.Y, centerCords.Coords2.X, centerCords.Coords2.Y, color);
  60. }
  61. /// <summary>
  62. /// Updates coordinates in order to some tools work
  63. /// </summary>
  64. /// <param name="cords">Current coordinates</param>
  65. public void UpdateCoordinates(Coordinates cords)
  66. {
  67. _activeCoordinates = cords;
  68. }
  69. /// <summary>
  70. /// Fills area with color (forest fire alghoritm)
  71. /// </summary>
  72. /// <param name="canvas">Bitmap to operate on</param>
  73. /// <param name="pixelPosition">Position of starting pixel</param>
  74. /// <param name="color">Fills area with this color</param>
  75. private WriteableBitmap FloodFill(WriteableBitmap canvas, Coordinates pixelPosition, Color color)
  76. {
  77. WriteableBitmap bm = canvas;
  78. Color colorToReplace = bm.GetPixel(pixelPosition.X, pixelPosition.Y);
  79. var stack = new Stack<Tuple<int, int>>();
  80. stack.Push(Tuple.Create(pixelPosition.X, pixelPosition.Y));
  81. while (stack.Count > 0)
  82. {
  83. var point = stack.Pop();
  84. if (point.Item1 < 0 || point.Item1 > bm.Height - 1) continue;
  85. if (point.Item2 < 0 || point.Item2 > bm.Width - 1) continue;
  86. if (bm.GetPixel(point.Item1, point.Item2) == color) continue;
  87. if (bm.GetPixel(point.Item1, point.Item2) == colorToReplace)
  88. {
  89. bm.SetPixel(point.Item1, point.Item2, color);
  90. stack.Push(Tuple.Create(point.Item1, point.Item2 - 1));
  91. stack.Push(Tuple.Create(point.Item1 + 1, point.Item2));
  92. stack.Push(Tuple.Create(point.Item1, point.Item2 + 1));
  93. stack.Push(Tuple.Create(point.Item1 - 1, point.Item2));
  94. }
  95. }
  96. return bm;
  97. }
  98. /// <summary>
  99. /// Draws line in canvas
  100. /// </summary>
  101. /// <param name="layer">Layer to operate on</param>
  102. /// <param name="coordinates">Starting coordinates, usually click point</param>
  103. /// <param name="color">Does it really need a description?</param>
  104. private async void LineAsync(Layer layer, Coordinates coordinates, Color color, int size)
  105. {
  106. WriteableBitmap wb = layer.LayerBitmap;
  107. _toolIsExecuting = true;
  108. //clones bitmap before line
  109. WriteableBitmap writeableBitmap = wb.Clone();
  110. //While Mouse buttons are pressed, clears current bitmap, pastes cloned bitmap and draws line, on each iteration
  111. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  112. {
  113. wb.Clear();
  114. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  115. wb.DrawLineBresenham(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  116. await Task.Delay(_asyncDelay);
  117. }
  118. _toolIsExecuting = false;
  119. }
  120. /// <summary>
  121. /// Draws circle on bitmap.
  122. /// </summary>
  123. /// <param name="layer">Layer to operate on.</param>
  124. /// <param name="coordinates">Starting pixel coordinates.</param>
  125. /// <param name="color">Circle color.</param>
  126. private async void CircleAsync(Layer layer, Coordinates coordinates, Color color)
  127. {
  128. WriteableBitmap wb = layer.LayerBitmap;
  129. //Basically does the same like rectangle method, but with different shape
  130. _toolIsExecuting = true;
  131. WriteableBitmap bitmap = wb.Clone();
  132. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  133. {
  134. wb.Clear();
  135. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), bitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  136. if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y)
  137. {
  138. wb.DrawEllipse(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color);
  139. }
  140. else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y)
  141. {
  142. wb.DrawEllipse(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  143. }
  144. else if (coordinates.Y > _activeCoordinates.Y)
  145. {
  146. wb.DrawEllipse(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color);
  147. }
  148. else
  149. {
  150. wb.DrawEllipse(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color);
  151. }
  152. await Task.Delay(_asyncDelay);
  153. }
  154. _toolIsExecuting = false;
  155. }
  156. /// <summary>
  157. /// Draws rectangle on bitmap
  158. /// </summary>
  159. /// <param name="layer">Layer to operate on</param>
  160. /// <param name="coordinates">Starting pixel coordinate</param>
  161. /// <param name="color">Rectangle color</param>
  162. private async void RectangleAsync(Layer layer, Coordinates coordinates, Color color)
  163. {
  164. WriteableBitmap wb = layer.LayerBitmap;
  165. _toolIsExecuting = true;
  166. WriteableBitmap writeableBitmap = wb.Clone();
  167. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  168. {
  169. //Two lines below are responsible for clearing last rectangle (on mouse move), to live show rectangle on bitmap
  170. wb.Clear();
  171. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  172. //Those ifs are changing direction of rectangle. In other words: flips rectangle on X and Y axis when needed
  173. if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y)
  174. {
  175. wb.DrawRectangle(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color);
  176. }
  177. else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y)
  178. {
  179. wb.DrawRectangle(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  180. }
  181. else if (coordinates.Y > _activeCoordinates.Y)
  182. {
  183. wb.DrawRectangle(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color);
  184. }
  185. else
  186. {
  187. wb.DrawRectangle(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color);
  188. }
  189. await Task.Delay(_asyncDelay);
  190. }
  191. _toolIsExecuting = false;
  192. }
  193. /// <summary>
  194. /// Returns color of pixel.
  195. /// </summary>
  196. /// <param name="layer">Layer in which bitmap with pixels are stored.</param>
  197. /// <param name="coordinates">Pixel coordinate.</param>
  198. /// <returns></returns>
  199. public static Color ColorPicker(Layer layer, Coordinates coordinates)
  200. {
  201. return layer.LayerBitmap.GetPixel(coordinates.X, coordinates.Y);
  202. }
  203. /// <summary>
  204. /// Ligtens pixel color.
  205. /// </summary>
  206. /// <param name="bitmap">Bitmap to work on.</param>
  207. /// <param name="coordinates">Pixel coordinates.</param>
  208. /// <returns></returns>
  209. private WriteableBitmap Lighten(WriteableBitmap bitmap, Coordinates coordinates)
  210. {
  211. WriteableBitmap wb = bitmap;
  212. Color pixel = wb.GetPixel(coordinates.X, coordinates.Y);
  213. Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R, pixel.G, pixel.B), 0.1f);
  214. wb.SetPixel(coordinates.X, coordinates.Y, newColor);
  215. return wb;
  216. }
  217. /// <summary>
  218. /// Darkens pixel color.
  219. /// </summary>
  220. /// <param name="bitmap">Bitmap to work on.</param>
  221. /// <param name="coordinates">Pixel coordinates.</param>
  222. /// <returns></returns>
  223. private WriteableBitmap Darken(WriteableBitmap bitmap, Coordinates coordinates)
  224. {
  225. WriteableBitmap wb = bitmap;
  226. Color pixel = wb.GetPixel(coordinates.X, coordinates.Y);
  227. Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R,pixel.G,pixel.B), -0.06f);
  228. wb.SetPixel(coordinates.X, coordinates.Y, newColor);
  229. return wb;
  230. }
  231. }
  232. }