ToolCalculator.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using PixiEditor.Helpers;
  2. using PixiEditor.Helpers.Extensions;
  3. using PixiEditor.Models.Layers;
  4. using PixiEditor.Models.Position;
  5. using PixiEditor.Models.Tools;
  6. using PixiEditor.Models.Tools.Tools;
  7. using SkiaSharp;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics;
  11. using System.Linq;
  12. using System.Windows;
  13. using System.Windows.Input;
  14. namespace PixiEditor.Models.ImageManipulation
  15. {
  16. public static class ToolCalculator
  17. {
  18. /// <summary>
  19. /// This function calculates fill and outputs it into Coordinates list.
  20. /// </summary>
  21. /// <remarks>All coordinates are calculated without layer offset.
  22. /// If you want to take consideration offset, just do it in CalculateBresenhamLine in PerformLinearFill function.</remarks>
  23. /// <param name="layer">Layer to calculate fill from.</param>
  24. /// <param name="startingCoords">Starting fill coordinates</param>
  25. /// <param name="maxWidth">Maximum fill width</param>
  26. /// <param name="maxHeight">Maximum fill height</param>
  27. /// <param name="newColor">Replacement color to stop on</param>
  28. /// <param name="output">List with output coordinates.</param>
  29. public static void GetLinearFillAbsolute(Layer layer, Coordinates startingCoords, int maxWidth, int maxHeight, SKColor newColor, List<Coordinates> output)
  30. {
  31. Queue<FloodFillRange> floodFillQueue = new Queue<FloodFillRange>();
  32. SKColor colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
  33. if ((colorToReplace.Alpha == 0 && newColor.Alpha == 0) ||
  34. colorToReplace == newColor)
  35. return;
  36. int width = maxWidth;
  37. int height = maxHeight;
  38. if (startingCoords.X < 0 || startingCoords.Y < 0 || startingCoords.X >= width || startingCoords.Y >= height)
  39. return;
  40. var visited = new bool[width * height];
  41. Int32Rect dirtyRect = new Int32Rect(startingCoords.X, startingCoords.Y, 1, 1);
  42. PerformLinearFill(layer, floodFillQueue, startingCoords, width, colorToReplace, ref dirtyRect, visited, output);
  43. PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, visited, output);
  44. }
  45. public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
  46. List<Coordinates> output)
  47. {
  48. DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
  49. EllipseGenerator.GenerateEllipseFromRect(fixedCoordinates, output);
  50. if (fill)
  51. {
  52. CalculateFillForEllipse(output);
  53. }
  54. }
  55. public static void GenerateRectangleNonAlloc(
  56. Coordinates start,
  57. Coordinates end, bool fill, int thickness, List<Coordinates> output)
  58. {
  59. DoubleCoords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
  60. CalculateRectanglePoints(fixedCoordinates, output);
  61. for (int i = 1; i < (int)Math.Floor(thickness / 2f) + 1; i++)
  62. {
  63. CalculateRectanglePoints(
  64. new DoubleCoords(
  65. new Coordinates(fixedCoordinates.Coords1.X - i, fixedCoordinates.Coords1.Y - i),
  66. new Coordinates(fixedCoordinates.Coords2.X + i, fixedCoordinates.Coords2.Y + i)), output);
  67. }
  68. for (int i = 1; i < (int)Math.Ceiling(thickness / 2f); i++)
  69. {
  70. CalculateRectanglePoints(
  71. new DoubleCoords(
  72. new Coordinates(fixedCoordinates.Coords1.X + i, fixedCoordinates.Coords1.Y + i),
  73. new Coordinates(fixedCoordinates.Coords2.X - i, fixedCoordinates.Coords2.Y - i)), output);
  74. }
  75. if (fill)
  76. {
  77. CalculateRectangleFillNonAlloc(start, end, thickness, output);
  78. }
  79. }
  80. public static DoubleCoords CalculateCoordinatesForShapeRotation(
  81. Coordinates startingCords,
  82. Coordinates secondCoordinates)
  83. {
  84. Coordinates currentCoordinates = secondCoordinates;
  85. if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
  86. {
  87. return new DoubleCoords(
  88. new Coordinates(currentCoordinates.X, currentCoordinates.Y),
  89. new Coordinates(startingCords.X, startingCords.Y));
  90. }
  91. if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
  92. {
  93. return new DoubleCoords(
  94. new Coordinates(startingCords.X, startingCords.Y),
  95. new Coordinates(currentCoordinates.X, currentCoordinates.Y));
  96. }
  97. if (startingCords.Y > currentCoordinates.Y)
  98. {
  99. return new DoubleCoords(
  100. new Coordinates(startingCords.X, currentCoordinates.Y),
  101. new Coordinates(currentCoordinates.X, startingCords.Y));
  102. }
  103. if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
  104. {
  105. return new DoubleCoords(
  106. new Coordinates(currentCoordinates.X, startingCords.Y),
  107. new Coordinates(startingCords.X, currentCoordinates.Y));
  108. }
  109. return new DoubleCoords(startingCords, secondCoordinates);
  110. }
  111. private static void PerformLinearFill(
  112. Layer layer, Queue<FloodFillRange> floodFillQueue,
  113. Coordinates coords, int width, SKColor colorToReplace, ref Int32Rect dirtyRect, bool[] visited, List<Coordinates> output)
  114. {
  115. // Find the Left Edge of the Color Area
  116. int fillXLeft = coords.X;
  117. while (true)
  118. {
  119. // Indicate that this pixel has been checked
  120. int pixelIndex = (coords.Y * width) + fillXLeft;
  121. visited[pixelIndex] = true;
  122. // Move one pixel to the left
  123. fillXLeft--;
  124. // Exit the loop if we're at edge of the bitmap or the color area
  125. if (fillXLeft < 0 || visited[pixelIndex - 1] || layer.GetPixelWithOffset(fillXLeft, coords.Y) != colorToReplace)
  126. break;
  127. }
  128. int lastCheckedPixelLeft = fillXLeft + 1;
  129. // Find the Right Edge of the Color Area
  130. int fillXRight = coords.X;
  131. while (true)
  132. {
  133. int pixelIndex = (coords.Y * width) + fillXRight;
  134. visited[pixelIndex] = true;
  135. fillXRight++;
  136. if (fillXRight >= width || visited[pixelIndex + 1] || layer.GetPixelWithOffset(fillXRight, coords.Y) != colorToReplace)
  137. break;
  138. }
  139. int lastCheckedPixelRight = fillXRight - 1;
  140. int relativeY = coords.Y;
  141. LineTool.CalculateBresenhamLine(new Coordinates(lastCheckedPixelLeft, relativeY), new Coordinates(lastCheckedPixelRight, relativeY), output);
  142. dirtyRect = dirtyRect.Expand(new Int32Rect(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));
  143. FloodFillRange range = new FloodFillRange(lastCheckedPixelLeft, lastCheckedPixelRight, coords.Y);
  144. floodFillQueue.Enqueue(range);
  145. }
  146. private static void PerformFloodFIll(
  147. Layer layer, Queue<FloodFillRange> floodFillQueue,
  148. SKColor colorToReplace, ref Int32Rect dirtyRect, int width, int height, bool[] pixelsVisited, List<Coordinates> output)
  149. {
  150. while (floodFillQueue.Count > 0)
  151. {
  152. FloodFillRange range = floodFillQueue.Dequeue();
  153. //START THE LOOP UPWARDS AND DOWNWARDS
  154. int upY = range.Y - 1; //so we can pass the y coord by ref
  155. int downY = range.Y + 1;
  156. int downPixelxIndex = (width * (range.Y + 1)) + range.StartX;
  157. int upPixelIndex = (width * (range.Y - 1)) + range.StartX;
  158. for (int i = range.StartX; i <= range.EndX; i++)
  159. {
  160. //START LOOP UPWARDS
  161. //if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
  162. if (range.Y > 0 && (!pixelsVisited[upPixelIndex]) && layer.GetPixelWithOffset(i, upY) == colorToReplace)
  163. PerformLinearFill(layer, floodFillQueue, new Coordinates(i, upY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
  164. //START LOOP DOWNWARDS
  165. if (range.Y < (height - 1) && (!pixelsVisited[downPixelxIndex]) && layer.GetPixelWithOffset(i, downY) == colorToReplace)
  166. PerformLinearFill(layer, floodFillQueue, new Coordinates(i, downY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
  167. downPixelxIndex++;
  168. upPixelIndex++;
  169. }
  170. }
  171. }
  172. private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
  173. {
  174. if (!outlineCoordinates.Any())
  175. return;
  176. var lines = EllipseGenerator.SplitEllipseIntoLines(outlineCoordinates);
  177. foreach (var line in lines)
  178. {
  179. for (int i = line.Coords1.X; i <= line.Coords2.X; i++)
  180. {
  181. outlineCoordinates.Add(new Coordinates(i, line.Coords1.Y));
  182. }
  183. }
  184. }
  185. private static void CalculateRectangleFillNonAlloc(Coordinates start, Coordinates end, int thickness, List<Coordinates> output)
  186. {
  187. int offset = (int)Math.Ceiling(thickness / 2f);
  188. DoubleCoords fixedCords = CalculateCoordinatesForShapeRotation(start, end);
  189. DoubleCoords innerCords = new DoubleCoords
  190. {
  191. Coords1 = new Coordinates(fixedCords.Coords1.X + offset, fixedCords.Coords1.Y + offset),
  192. Coords2 = new Coordinates(fixedCords.Coords2.X - (offset - 1), fixedCords.Coords2.Y - (offset - 1))
  193. };
  194. int height = innerCords.Coords2.Y - innerCords.Coords1.Y;
  195. int width = innerCords.Coords2.X - innerCords.Coords1.X;
  196. if (height < 1 || width < 1)
  197. {
  198. return;
  199. }
  200. int i = 0;
  201. for (int y = 0; y < height; y++)
  202. {
  203. for (int x = 0; x < width; x++)
  204. {
  205. output.Add(new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y));
  206. i++;
  207. }
  208. }
  209. }
  210. private static void CalculateRectanglePoints(DoubleCoords coordinates, List<Coordinates> output)
  211. {
  212. for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
  213. {
  214. output.Add(new Coordinates(i, coordinates.Coords1.Y));
  215. output.Add(new Coordinates(i, coordinates.Coords2.Y));
  216. }
  217. for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
  218. {
  219. output.Add(new Coordinates(coordinates.Coords1.X, i));
  220. output.Add(new Coordinates(coordinates.Coords2.X, i));
  221. }
  222. }
  223. }
  224. }