ToolSet.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. private Coordinates _activeCoordinates = new Coordinates();
  18. private bool _toolIsExecuting = false;
  19. private int _asyncDelay = 15;
  20. private WriteableBitmap _oldBitmap;
  21. /// <summary>
  22. /// Executes tool action
  23. /// </summary>
  24. /// <param name="layer">Layer to operate on.</param>
  25. /// <param name="startingCoords">Click coordinates.</param>
  26. /// <param name="color">Color that tool will use.</param>
  27. /// <param name="toolSize">Size/thickness of tool</param>
  28. /// <param name="tool">Tool to execute</param>
  29. /// <returns></returns>
  30. public Layer ExecuteTool(Layer layer, Coordinates startingCoords, Color color,int toolSize, ToolType tool)
  31. {
  32. if (toolSize < 1) return null;
  33. Layer cLayer = layer;
  34. _oldBitmap = new WriteableBitmap(layer.LayerBitmap);
  35. switch (tool)
  36. {
  37. case ToolType.Pen:
  38. cLayer.LayerBitmap = DrawPixel(cLayer.LayerBitmap, startingCoords, toolSize,color);
  39. break;
  40. case ToolType.Bucket:
  41. cLayer.LayerBitmap = FloodFill(cLayer.LayerBitmap, startingCoords, color);
  42. break;
  43. case ToolType.Line:
  44. if (_toolIsExecuting == false)
  45. {
  46. LineAsync(cLayer, startingCoords, color, toolSize);
  47. }
  48. break;
  49. case ToolType.Circle:
  50. if(_toolIsExecuting == false)
  51. {
  52. CircleAsync(cLayer, startingCoords, color);
  53. }
  54. break;
  55. case ToolType.Rectangle:
  56. if(_toolIsExecuting == false)
  57. {
  58. RectangleAsync(cLayer, startingCoords, color);
  59. }
  60. break;
  61. case ToolType.Earser:
  62. cLayer.LayerBitmap = DrawPixel(cLayer.LayerBitmap, startingCoords, toolSize, Colors.Transparent);
  63. break;
  64. case ToolType.Lighten:
  65. if(Mouse.LeftButton == MouseButtonState.Pressed)
  66. {
  67. cLayer.LayerBitmap = Lighten(cLayer.LayerBitmap, startingCoords);
  68. }
  69. else if(Mouse.RightButton == MouseButtonState.Pressed)
  70. {
  71. cLayer.LayerBitmap = Darken(cLayer.LayerBitmap, startingCoords);
  72. }
  73. break;
  74. }
  75. if (tool != ToolType.ColorPicker)
  76. {
  77. UndoManager.RecordChanges("ActiveLightLayer", new LightLayer(_oldBitmap.ToByteArray(), (int)_oldBitmap.Height, (int)_oldBitmap.Width),
  78. $"{tool.ToString()} Tool.");
  79. }
  80. return cLayer;
  81. }
  82. /// <summary>
  83. /// Not working yet.
  84. /// </summary>
  85. /// <param name="bitmap"></param>
  86. /// <param name="pixelCoordinates"></param>
  87. /// <param name="color"></param>
  88. /// <param name="highlightThickness"></param>
  89. public static void HighlightPixel(WriteableBitmap bitmap,Coordinates pixelCoordinates, Color color, int highlightThickness)
  90. {
  91. bitmap.Clear();
  92. bitmap.Blit(new Rect(new Size(bitmap.Width, bitmap.Height)), bitmap, new Rect(new Size(bitmap.Width, bitmap.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  93. DCords centerCords = CalculateThicknessCenter(pixelCoordinates, highlightThickness);
  94. bitmap.FillRectangle(centerCords.Coords1.X, centerCords.Coords1.Y, centerCords.Coords2.X, centerCords.Coords2.Y, color);
  95. }
  96. /// <summary>
  97. /// Updates coordinates in order to some tools work
  98. /// </summary>
  99. /// <param name="cords">Current coordinates</param>
  100. public void UpdateCoordinates(Coordinates cords)
  101. {
  102. _activeCoordinates = cords;
  103. }
  104. /// <summary>
  105. /// Fills pixel(s) with choosen color
  106. /// </summary>
  107. /// <param name="canvas">Bitmap to operate on.</param>
  108. /// <param name="pixelPosition">Coordinates of pixel.</param>
  109. /// <param name="color">Color to be set.</param>
  110. private WriteableBitmap DrawPixel(WriteableBitmap canvas, Coordinates pixelPosition,int thickness,Color color)
  111. {
  112. WriteableBitmap bm = canvas;
  113. int x1, y1, x2, y2;
  114. DCords centeredCoords = CalculateThicknessCenter(pixelPosition, thickness);
  115. x1 = centeredCoords.Coords1.X;
  116. y1 = centeredCoords.Coords1.Y;
  117. x2 = centeredCoords.Coords2.X;
  118. y2 = centeredCoords.Coords2.Y;
  119. bm.FillRectangle(x1, y1, x2, y2, color);
  120. return bm;
  121. }
  122. /// <summary>
  123. /// Calculates center of thickness * thickness rectangle
  124. /// </summary>
  125. /// <param name="startPosition">Top left position of rectangle</param>
  126. /// <param name="thickness">Thickness of rectangle</param>
  127. /// <returns></returns>
  128. private static DCords CalculateThicknessCenter(Coordinates startPosition, int thickness)
  129. {
  130. int x1, x2, y1, y2;
  131. if (thickness % 2 == 0)
  132. {
  133. x2 = startPosition.X + thickness / 2;
  134. y2 = startPosition.Y + thickness / 2;
  135. x1 = x2 - thickness;
  136. y1 = y2 - thickness;
  137. }
  138. else
  139. {
  140. x2 = startPosition.X + (((thickness - 1) / 2) + 1);
  141. y2 = startPosition.Y + (((thickness - 1) / 2) + 1);
  142. x1 = x2 - thickness;
  143. y1 = y2 - thickness;
  144. }
  145. return new DCords(new Coordinates(x1, y1), new Coordinates(x2, y2));
  146. }
  147. /// <summary>
  148. /// Fills area with color (forest fire alghoritm)
  149. /// </summary>
  150. /// <param name="canvas">Bitmap to operate on</param>
  151. /// <param name="pixelPosition">Position of starting pixel</param>
  152. /// <param name="color">Fills area with this color</param>
  153. private WriteableBitmap FloodFill(WriteableBitmap canvas, Coordinates pixelPosition, Color color)
  154. {
  155. WriteableBitmap bm = canvas;
  156. Color colorToReplace = bm.GetPixel(pixelPosition.X, pixelPosition.Y);
  157. var stack = new Stack<Tuple<int, int>>();
  158. stack.Push(Tuple.Create(pixelPosition.X, pixelPosition.Y));
  159. while (stack.Count > 0)
  160. {
  161. var point = stack.Pop();
  162. if (point.Item1 < 0 || point.Item1 > bm.Height - 1) continue;
  163. if (point.Item2 < 0 || point.Item2 > bm.Width - 1) continue;
  164. if (bm.GetPixel(point.Item1, point.Item2) == color) continue;
  165. if (bm.GetPixel(point.Item1, point.Item2) == colorToReplace)
  166. {
  167. bm.SetPixel(point.Item1, point.Item2, color);
  168. stack.Push(Tuple.Create(point.Item1, point.Item2 - 1));
  169. stack.Push(Tuple.Create(point.Item1 + 1, point.Item2));
  170. stack.Push(Tuple.Create(point.Item1, point.Item2 + 1));
  171. stack.Push(Tuple.Create(point.Item1 - 1, point.Item2));
  172. }
  173. }
  174. return bm;
  175. }
  176. /// <summary>
  177. /// Draws line in canvas
  178. /// </summary>
  179. /// <param name="layer">Layer to operate on</param>
  180. /// <param name="coordinates">Starting coordinates, usually click point</param>
  181. /// <param name="color">Does it really need a description?</param>
  182. private async void LineAsync(Layer layer, Coordinates coordinates, Color color, int size)
  183. {
  184. WriteableBitmap wb = layer.LayerBitmap;
  185. _toolIsExecuting = true;
  186. //clones bitmap before line
  187. WriteableBitmap writeableBitmap = wb.Clone();
  188. //While Mouse buttons are pressed, clears current bitmap, pastes cloned bitmap and draws line, on each iteration
  189. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  190. {
  191. wb.Clear();
  192. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  193. wb.DrawLineBresenham(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  194. await Task.Delay(_asyncDelay);
  195. }
  196. _toolIsExecuting = false;
  197. }
  198. /// <summary>
  199. /// Draws circle on bitmap.
  200. /// </summary>
  201. /// <param name="layer">Layer to operate on.</param>
  202. /// <param name="coordinates">Starting pixel coordinates.</param>
  203. /// <param name="color">Circle color.</param>
  204. private async void CircleAsync(Layer layer, Coordinates coordinates, Color color)
  205. {
  206. WriteableBitmap wb = layer.LayerBitmap;
  207. //Basically does the same like rectangle method, but with different shape
  208. _toolIsExecuting = true;
  209. WriteableBitmap bitmap = wb.Clone();
  210. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  211. {
  212. wb.Clear();
  213. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), bitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  214. if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y)
  215. {
  216. wb.DrawEllipse(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color);
  217. }
  218. else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y)
  219. {
  220. wb.DrawEllipse(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  221. }
  222. else if (coordinates.Y > _activeCoordinates.Y)
  223. {
  224. wb.DrawEllipse(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color);
  225. }
  226. else
  227. {
  228. wb.DrawEllipse(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color);
  229. }
  230. await Task.Delay(_asyncDelay);
  231. }
  232. _toolIsExecuting = false;
  233. }
  234. /// <summary>
  235. /// Draws rectangle on bitmap
  236. /// </summary>
  237. /// <param name="layer">Layer to operate on</param>
  238. /// <param name="coordinates">Starting pixel coordinate</param>
  239. /// <param name="color">Rectangle color</param>
  240. private async void RectangleAsync(Layer layer, Coordinates coordinates, Color color)
  241. {
  242. WriteableBitmap wb = layer.LayerBitmap;
  243. _toolIsExecuting = true;
  244. WriteableBitmap writeableBitmap = wb.Clone();
  245. while (Mouse.LeftButton == MouseButtonState.Pressed || Mouse.RightButton == MouseButtonState.Pressed)
  246. {
  247. //Two lines below are responsible for clearing last rectangle (on mouse move), to live show rectangle on bitmap
  248. wb.Clear();
  249. wb.Blit(new Rect(new Size(layer.Width, layer.Height)), writeableBitmap, new Rect(new Size(layer.Width, layer.Height)), WriteableBitmapExtensions.BlendMode.Additive);
  250. //Those ifs are changing direction of rectangle. In other words: flips rectangle on X and Y axis when needed
  251. if (coordinates.X > _activeCoordinates.X && coordinates.Y > _activeCoordinates.Y)
  252. {
  253. wb.DrawRectangle(_activeCoordinates.X, _activeCoordinates.Y, coordinates.X, coordinates.Y, color);
  254. }
  255. else if (coordinates.X < _activeCoordinates.X && coordinates.Y < _activeCoordinates.Y)
  256. {
  257. wb.DrawRectangle(coordinates.X, coordinates.Y, _activeCoordinates.X, _activeCoordinates.Y, color);
  258. }
  259. else if (coordinates.Y > _activeCoordinates.Y)
  260. {
  261. wb.DrawRectangle(coordinates.X, _activeCoordinates.Y, _activeCoordinates.X, coordinates.Y, color);
  262. }
  263. else
  264. {
  265. wb.DrawRectangle(_activeCoordinates.X, coordinates.Y, coordinates.X, _activeCoordinates.Y, color);
  266. }
  267. await Task.Delay(_asyncDelay);
  268. }
  269. _toolIsExecuting = false;
  270. }
  271. /// <summary>
  272. /// Returns color of pixel.
  273. /// </summary>
  274. /// <param name="layer">Layer in which bitmap with pixels are stored.</param>
  275. /// <param name="coordinates">Pixel coordinate.</param>
  276. /// <returns></returns>
  277. public static Color ColorPicker(Layer layer, Coordinates coordinates)
  278. {
  279. return layer.LayerBitmap.GetPixel(coordinates.X, coordinates.Y);
  280. }
  281. /// <summary>
  282. /// Ligtens pixel color.
  283. /// </summary>
  284. /// <param name="bitmap">Bitmap to work on.</param>
  285. /// <param name="coordinates">Pixel coordinates.</param>
  286. /// <returns></returns>
  287. private WriteableBitmap Lighten(WriteableBitmap bitmap, Coordinates coordinates)
  288. {
  289. WriteableBitmap wb = bitmap;
  290. Color pixel = wb.GetPixel(coordinates.X, coordinates.Y);
  291. Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R, pixel.G, pixel.B), 0.1f);
  292. wb.SetPixel(coordinates.X, coordinates.Y, newColor);
  293. return wb;
  294. }
  295. /// <summary>
  296. /// Darkens pixel color.
  297. /// </summary>
  298. /// <param name="bitmap">Bitmap to work on.</param>
  299. /// <param name="coordinates">Pixel coordinates.</param>
  300. /// <returns></returns>
  301. private WriteableBitmap Darken(WriteableBitmap bitmap, Coordinates coordinates)
  302. {
  303. WriteableBitmap wb = bitmap;
  304. Color pixel = wb.GetPixel(coordinates.X, coordinates.Y);
  305. Color newColor = ExColor.ChangeColorBrightness(System.Drawing.Color.FromArgb(pixel.R,pixel.G,pixel.B), -0.06f);
  306. wb.SetPixel(coordinates.X, coordinates.Y, newColor);
  307. return wb;
  308. }
  309. }
  310. }