OperationHelper.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. using ChunkyImageLib.DataHolders;
  2. using SkiaSharp;
  3. namespace ChunkyImageLib.Operations;
  4. public static class OperationHelper
  5. {
  6. public static VecI ConvertForResolution(VecI pixelPos, ChunkResolution resolution)
  7. {
  8. var mult = resolution.Multiplier();
  9. return new((int)Math.Round(pixelPos.X * mult), (int)Math.Round(pixelPos.Y * mult));
  10. }
  11. public static VecD ConvertForResolution(VecD pixelPos, ChunkResolution resolution)
  12. {
  13. var mult = resolution.Multiplier();
  14. return new(pixelPos.X * mult, pixelPos.Y * mult);
  15. }
  16. /// <summary>
  17. /// toModify[x,y].Alpha = Math.Min(toModify[x,y].Alpha, toGetAlphaFrom[x,y].Alpha)
  18. /// </summary>
  19. public unsafe static void ClampAlpha(SKSurface toModify, SKSurface toGetAlphaFrom)
  20. {
  21. using (var map = toModify.PeekPixels())
  22. {
  23. using (var refMap = toGetAlphaFrom.PeekPixels())
  24. {
  25. long* pixels = (long*)map.GetPixels();
  26. long* refPixels = (long*)refMap.GetPixels();
  27. int size = map.Width * map.Height;
  28. if (map.Width != refMap.Width || map.Height != refMap.Height)
  29. throw new ArgumentException("The surfaces must have the same size");
  30. for (int i = 0; i < size; i++)
  31. {
  32. long* offset = pixels + i;
  33. long* refOffset = refPixels + i;
  34. Half* alpha = (Half*)offset + 3;
  35. Half* refAlpha = (Half*)refOffset + 3;
  36. if (*refAlpha < *alpha)
  37. {
  38. float a = (float)(*alpha);
  39. float r = (float)(*((Half*)offset)) / a;
  40. float g = (float)(*((Half*)offset + 1)) / a;
  41. float b = (float)(*((Half*)offset + 2)) / a;
  42. float newA = (float)(*refAlpha);
  43. Half newR = (Half)(r * newA);
  44. Half newG = (Half)(g * newA);
  45. Half newB = (Half)(b * newA);
  46. *offset = ((long)*(ushort*)(&newR)) | ((long)*(ushort*)(&newG)) << 16 | ((long)*(ushort*)(&newB)) << 32 | ((long)*(ushort*)(refAlpha)) << 48;
  47. }
  48. }
  49. }
  50. }
  51. }
  52. public static ShapeCorners ConvertForResolution(ShapeCorners corners, ChunkResolution resolution)
  53. {
  54. return new ShapeCorners()
  55. {
  56. BottomLeft = ConvertForResolution(corners.BottomLeft, resolution),
  57. BottomRight = ConvertForResolution(corners.BottomRight, resolution),
  58. TopLeft = ConvertForResolution(corners.TopLeft, resolution),
  59. TopRight = ConvertForResolution(corners.TopRight, resolution),
  60. };
  61. }
  62. public static VecI GetChunkPos(VecI pixelPos, int chunkSize)
  63. {
  64. return new VecI()
  65. {
  66. X = (int)MathF.Floor(pixelPos.X / (float)chunkSize),
  67. Y = (int)MathF.Floor(pixelPos.Y / (float)chunkSize)
  68. };
  69. }
  70. public static SKMatrix CreateMatrixFromPoints(ShapeCorners corners, VecD size)
  71. => CreateMatrixFromPoints((SKPoint)corners.TopLeft, (SKPoint)corners.TopRight, (SKPoint)corners.BottomRight, (SKPoint)corners.BottomLeft, (float)size.X, (float)size.Y);
  72. public static SKMatrix CreateMatrixFromPoints(SKPoint topLeft, SKPoint topRight, SKPoint botRight, SKPoint botLeft, float width, float height)
  73. {
  74. (float x1, float y1) = (topLeft.X, topLeft.Y);
  75. (float x2, float y2) = (topRight.X, topRight.Y);
  76. (float x3, float y3) = (botRight.X, botRight.Y);
  77. (float x4, float y4) = (botLeft.X, botLeft.Y);
  78. (float w, float h) = (width, height);
  79. float scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  80. float skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  81. float transX = x1;
  82. float skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  83. float scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  84. float transY = y1;
  85. float persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
  86. float persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
  87. float persp2 = 1;
  88. return new SKMatrix(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2);
  89. }
  90. public static HashSet<VecI> FindChunksTouchingQuadrilateral(ShapeCorners corners, int chunkSize)
  91. {
  92. if (corners.HasNaNOrInfinity ||
  93. (corners.BottomLeft - corners.TopRight).Length > chunkSize * 40 * 20 ||
  94. (corners.TopLeft - corners.BottomRight).Length > chunkSize * 40 * 20)
  95. return new HashSet<VecI>();
  96. if (corners.IsInverted)
  97. corners = corners with { BottomLeft = corners.TopRight, TopRight = corners.BottomLeft };
  98. List<VecI>[] lines = new List<VecI>[] {
  99. FindChunksAlongLine(corners.TopRight, corners.TopLeft, chunkSize),
  100. FindChunksAlongLine(corners.BottomRight, corners.TopRight, chunkSize),
  101. FindChunksAlongLine(corners.BottomLeft, corners.BottomRight, chunkSize),
  102. FindChunksAlongLine(corners.TopLeft, corners.BottomLeft, chunkSize)
  103. };
  104. return FillLines(lines);
  105. }
  106. public static HashSet<VecI> FindChunksFullyInsideQuadrilateral(ShapeCorners corners, int chunkSize)
  107. {
  108. if (corners.HasNaNOrInfinity ||
  109. (corners.BottomLeft - corners.TopRight).Length > chunkSize * 40 * 20 ||
  110. (corners.TopLeft - corners.BottomRight).Length > chunkSize * 40 * 20)
  111. return new HashSet<VecI>();
  112. if (corners.IsInverted)
  113. corners = corners with { BottomLeft = corners.TopRight, TopRight = corners.BottomLeft };
  114. List<VecI>[] lines = new List<VecI>[] {
  115. FindChunksAlongLine(corners.TopLeft, corners.TopRight, chunkSize),
  116. FindChunksAlongLine(corners.TopRight, corners.BottomRight, chunkSize),
  117. FindChunksAlongLine(corners.BottomRight, corners.BottomLeft, chunkSize),
  118. FindChunksAlongLine(corners.BottomLeft, corners.TopLeft, chunkSize)
  119. };
  120. var output = FillLines(lines);
  121. //exclude lines
  122. for (int i = 0; i < lines.Length; i++)
  123. {
  124. output.ExceptWith(lines[i]);
  125. }
  126. return output;
  127. }
  128. /// <summary>
  129. /// Finds chunks that at least partially lie inside of a rectangle
  130. /// </summary>
  131. public static HashSet<VecI> FindChunksTouchingRectangle(VecD center, VecD size, double angle, int chunkSize)
  132. {
  133. if (size.X == 0 || size.Y == 0 || center.IsNaNOrInfinity() || size.IsNaNOrInfinity() || double.IsNaN(angle) || double.IsInfinity(angle))
  134. return new HashSet<VecI>();
  135. if (size.X > chunkSize * 40 * 20 || size.Y > chunkSize * 40 * 20)
  136. return new HashSet<VecI>();
  137. // draw a line on the outside of each side
  138. var corners = FindRectangleCorners(center, size, angle);
  139. List<VecI>[] lines = new List<VecI>[] {
  140. FindChunksAlongLine(corners.Item2, corners.Item1, chunkSize),
  141. FindChunksAlongLine(corners.Item3, corners.Item2, chunkSize),
  142. FindChunksAlongLine(corners.Item4, corners.Item3, chunkSize),
  143. FindChunksAlongLine(corners.Item1, corners.Item4, chunkSize)
  144. };
  145. if (lines[0].Count == 0 || lines[1].Count == 0 || lines[2].Count == 0 || lines[3].Count == 0)
  146. return new HashSet<VecI>();
  147. return FillLines(lines);
  148. }
  149. public static HashSet<VecI> FillLines(List<VecI>[] lines)
  150. {
  151. if (lines.Length == 0)
  152. return new HashSet<VecI>();
  153. //find min and max X for each Y in lines
  154. var ySel = (VecI vec) => vec.Y;
  155. int minY = int.MaxValue;
  156. int maxY = int.MinValue;
  157. foreach (var line in lines)
  158. {
  159. minY = Math.Min(line.Min(ySel), minY);
  160. maxY = Math.Max(line.Max(ySel), maxY);
  161. }
  162. int[] minXValues = new int[maxY - minY + 1];
  163. int[] maxXValues = new int[maxY - minY + 1];
  164. for (int i = 0; i < minXValues.Length; i++)
  165. {
  166. minXValues[i] = int.MaxValue;
  167. maxXValues[i] = int.MinValue;
  168. }
  169. for (int i = 0; i < lines.Length; i++)
  170. {
  171. UpdateMinXValues(lines[i], minXValues, minY);
  172. UpdateMaxXValues(lines[i], maxXValues, minY);
  173. }
  174. //draw a line from min X to max X for each Y
  175. HashSet<VecI> output = new();
  176. for (int i = 0; i < minXValues.Length; i++)
  177. {
  178. int minX = minXValues[i];
  179. int maxX = maxXValues[i];
  180. for (int x = minX; x <= maxX; x++)
  181. output.Add(new(x, i + minY));
  182. }
  183. return output;
  184. }
  185. public static HashSet<VecI> FindChunksFullyInsideRectangle(VecI pos, VecI size, int chunkSize)
  186. {
  187. if (size.X > chunkSize * 40 * 20 || size.Y > chunkSize * 40 * 20)
  188. return new HashSet<VecI>();
  189. VecI startChunk = GetChunkPos(pos, ChunkPool.FullChunkSize);
  190. VecI endChunk = GetChunkPosBiased(pos + size, false, false, chunkSize);
  191. HashSet<VecI> output = new();
  192. for (int x = startChunk.X; x <= endChunk.X; x++)
  193. {
  194. for (int y = startChunk.Y; y <= endChunk.Y; y++)
  195. {
  196. output.Add(new VecI(x, y));
  197. }
  198. }
  199. return output;
  200. }
  201. public static HashSet<VecI> FindChunksFullyInsideRectangle(VecD center, VecD size, double angle, int chunkSize)
  202. {
  203. if (size.X < chunkSize || size.Y < chunkSize || center.IsNaNOrInfinity() || size.IsNaNOrInfinity() || double.IsNaN(angle) || double.IsInfinity(angle))
  204. return new HashSet<VecI>();
  205. if (size.X > chunkSize * 40 * 20 || size.Y > chunkSize * 40 * 20)
  206. return new HashSet<VecI>();
  207. // draw a line on the inside of each side
  208. var corners = FindRectangleCorners(center, size, angle);
  209. List<VecI>[] lines = new List<VecI>[] {
  210. FindChunksAlongLine(corners.Item1, corners.Item2, chunkSize),
  211. FindChunksAlongLine(corners.Item2, corners.Item3, chunkSize),
  212. FindChunksAlongLine(corners.Item3, corners.Item4, chunkSize),
  213. FindChunksAlongLine(corners.Item4, corners.Item1, chunkSize)
  214. };
  215. var output = FillLines(lines);
  216. //exclude lines
  217. for (int i = 0; i < lines.Length; i++)
  218. {
  219. output.ExceptWith(lines[i]);
  220. }
  221. return output;
  222. }
  223. private static void UpdateMinXValues(List<VecI> line, int[] minXValues, int minY)
  224. {
  225. for (int i = 0; i < line.Count; i++)
  226. {
  227. if (line[i].X < minXValues[line[i].Y - minY])
  228. minXValues[line[i].Y - minY] = line[i].X;
  229. }
  230. }
  231. private static void UpdateMaxXValues(List<VecI> line, int[] maxXValues, int minY)
  232. {
  233. for (int i = 0; i < line.Count; i++)
  234. {
  235. if (line[i].X > maxXValues[line[i].Y - minY])
  236. maxXValues[line[i].Y - minY] = line[i].X;
  237. }
  238. }
  239. /// <summary>
  240. /// Think of this function as a line drawing algorithm.
  241. /// The chosen chunks are guaranteed to be on the left side of the line (assuming y going upwards and looking from p1 towards p2).
  242. /// This ensures that when you draw a filled shape all updated chunks will be covered (the filled part should go to the right of the line)
  243. /// No parts of the line will stick out to the left and be left uncovered
  244. /// </summary>
  245. public static List<VecI> FindChunksAlongLine(VecD p1, VecD p2, int chunkSize)
  246. {
  247. if (p1 == p2 || p1.IsNaNOrInfinity() || p2.IsNaNOrInfinity())
  248. return new List<VecI>();
  249. //rotate the line into the first quadrant of the coordinate plane
  250. int quadrant;
  251. if (p2.X >= p1.X && p2.Y >= p1.Y)
  252. {
  253. quadrant = 1;
  254. }
  255. else if (p2.X <= p1.X && p2.Y <= p1.Y)
  256. {
  257. quadrant = 3;
  258. p1 = -p1;
  259. p2 = -p2;
  260. }
  261. else if (p2.X < p1.X)
  262. {
  263. quadrant = 2;
  264. (p1.X, p1.Y) = (p1.Y, -p1.X);
  265. (p2.X, p2.Y) = (p2.Y, -p2.X);
  266. }
  267. else
  268. {
  269. quadrant = 4;
  270. (p1.X, p1.Y) = (-p1.Y, p1.X);
  271. (p2.X, p2.Y) = (-p2.Y, p2.X);
  272. }
  273. List<VecI> output = new();
  274. //vertical line
  275. if (p1.X == p2.X)
  276. {
  277. //if exactly on a chunk boundary, pick the chunk on the top-left
  278. VecI start = GetChunkPosBiased(p1, false, true, chunkSize);
  279. //if exactly on chunk boundary, pick the chunk on the bottom-left
  280. VecI end = GetChunkPosBiased(p2, false, false, chunkSize);
  281. for (int y = start.Y; y <= end.Y; y++)
  282. output.Add(new(start.X, y));
  283. }
  284. //horizontal line
  285. else if (p1.Y == p2.Y)
  286. {
  287. //if exactly on a chunk boundary, pick the chunk on the top-right
  288. VecI start = GetChunkPosBiased(p1, true, true, chunkSize);
  289. //if exactly on chunk boundary, pick the chunk on the top-left
  290. VecI end = GetChunkPosBiased(p2, false, true, chunkSize);
  291. for (int x = start.X; x <= end.X; x++)
  292. output.Add(new(x, start.Y));
  293. }
  294. //all other lines
  295. else
  296. {
  297. //y = mx + b
  298. double m = (p2.Y - p1.Y) / (p2.X - p1.X);
  299. double b = p1.Y - (p1.X * m);
  300. VecI cur = GetChunkPosBiased(p1, true, true, chunkSize);
  301. output.Add(cur);
  302. if (LineEq(m, cur.X * chunkSize + chunkSize, b) > cur.Y * chunkSize + chunkSize)
  303. cur.X--;
  304. VecI end = GetChunkPosBiased(p2, false, false, chunkSize);
  305. if (m < 1)
  306. {
  307. while (true)
  308. {
  309. if (LineEq(m, cur.X * chunkSize + chunkSize * 2, b) > cur.Y * chunkSize + chunkSize)
  310. {
  311. cur.X++;
  312. cur.Y++;
  313. }
  314. else
  315. {
  316. cur.X++;
  317. }
  318. if (cur.X >= end.X && cur.Y >= end.Y)
  319. break;
  320. output.Add(cur);
  321. }
  322. output.Add(end);
  323. }
  324. else
  325. {
  326. while (true)
  327. {
  328. if (LineEq(m, cur.X * chunkSize + chunkSize, b) <= cur.Y * chunkSize + chunkSize)
  329. {
  330. cur.X++;
  331. cur.Y++;
  332. }
  333. else
  334. {
  335. cur.Y++;
  336. }
  337. if (cur.X >= end.X && cur.Y >= end.Y)
  338. break;
  339. output.Add(cur);
  340. }
  341. output.Add(end);
  342. }
  343. }
  344. //rotate output back
  345. if (quadrant == 1)
  346. return output;
  347. if (quadrant == 3)
  348. {
  349. for (int i = 0; i < output.Count; i++)
  350. output[i] = new(-output[i].X - 1, -output[i].Y - 1);
  351. return output;
  352. }
  353. if (quadrant == 2)
  354. {
  355. for (int i = 0; i < output.Count; i++)
  356. output[i] = new(-output[i].Y - 1, output[i].X);
  357. return output;
  358. }
  359. for (int i = 0; i < output.Count; i++)
  360. output[i] = new(output[i].Y, -output[i].X - 1);
  361. return output;
  362. }
  363. private static double LineEq(double m, double x, double b)
  364. {
  365. return m * x + b;
  366. }
  367. public static VecI GetChunkPosBiased(VecD pos, bool positiveX, bool positiveY, int chunkSize)
  368. {
  369. pos /= chunkSize;
  370. return new VecI()
  371. {
  372. X = positiveX ? (int)Math.Floor(pos.X) : (int)Math.Ceiling(pos.X) - 1,
  373. Y = positiveY ? (int)Math.Floor(pos.Y) : (int)Math.Ceiling(pos.Y) - 1,
  374. };
  375. }
  376. /// <summary>
  377. /// Returns corners in ccw direction (assuming y points up)
  378. /// </summary>
  379. private static (VecD, VecD, VecD, VecD) FindRectangleCorners(VecD center, VecD size, double angle)
  380. {
  381. VecD right = VecD.FromAngleAndLength(angle, size.X / 2);
  382. VecD up = VecD.FromAngleAndLength(angle + Math.PI / 2, size.Y / 2);
  383. return (
  384. center + right + up,
  385. center - right + up,
  386. center - right - up,
  387. center + right - up
  388. );
  389. }
  390. }