EllipseOperation.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. using ChunkyImageLib.DataHolders;
  2. using PixiEditor.DrawingApi.Core.ColorsImpl;
  3. using PixiEditor.DrawingApi.Core.Numerics;
  4. using PixiEditor.DrawingApi.Core.Surfaces;
  5. using PixiEditor.DrawingApi.Core.Surfaces.PaintImpl;
  6. using PixiEditor.DrawingApi.Core.Surfaces.Vector;
  7. using PixiEditor.Numerics;
  8. namespace ChunkyImageLib.Operations;
  9. internal class EllipseOperation : IMirroredDrawOperation
  10. {
  11. public bool IgnoreEmptyChunks => false;
  12. private readonly RectI location;
  13. private readonly Color strokeColor;
  14. private readonly Color fillColor;
  15. private readonly int strokeWidth;
  16. private readonly double rotation;
  17. private readonly Paint paint;
  18. private bool init = false;
  19. private VectorPath? outerPath;
  20. private VectorPath? innerPath;
  21. private Point[]? ellipse;
  22. private Point[]? ellipseFill;
  23. private RectI? ellipseFillRect;
  24. public EllipseOperation(RectI location, Color strokeColor, Color fillColor, int strokeWidth, double rotationRad, Paint? paint = null)
  25. {
  26. this.location = location;
  27. this.strokeColor = strokeColor;
  28. this.fillColor = fillColor;
  29. this.strokeWidth = strokeWidth;
  30. this.rotation = rotationRad;
  31. this.paint = paint?.Clone() ?? new Paint();
  32. }
  33. private void Init()
  34. {
  35. init = true;
  36. if (strokeWidth == 1)
  37. {
  38. var ellipseList = EllipseHelper.GenerateEllipseFromRect(location, rotation);
  39. ellipse = ellipseList.Select(a => new Point(a)).Distinct().ToArray();
  40. if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
  41. {
  42. /*(var fill, ellipseFillRect) = EllipseHelper.SplitEllipseFillIntoRegions(ellipseList, location);
  43. ellipseFill = fill.Select(a => new Point(a)).ToArray();*/
  44. }
  45. }
  46. else
  47. {
  48. outerPath = new VectorPath();
  49. outerPath.ArcTo(location, 0, 359, true);
  50. innerPath = new VectorPath();
  51. innerPath.ArcTo(location.Inflate(-strokeWidth), 0, 359, true);
  52. }
  53. }
  54. public void DrawOnChunk(Chunk targetChunk, VecI chunkPos)
  55. {
  56. if (!init)
  57. Init();
  58. var surf = targetChunk.Surface.DrawingSurface;
  59. surf.Canvas.Save();
  60. surf.Canvas.Scale((float)targetChunk.Resolution.Multiplier());
  61. surf.Canvas.Translate(-chunkPos * ChunkyImage.FullChunkSize);
  62. paint.IsAntiAliased = targetChunk.Resolution != ChunkResolution.Full;
  63. if (strokeWidth == 1)
  64. {
  65. if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
  66. {
  67. /*paint.Color = fillColor;
  68. surf.Canvas.DrawPoints(PointMode.Lines, ellipseFill!, paint);
  69. surf.Canvas.DrawRect(ellipseFillRect!.Value, paint);*/
  70. }
  71. paint.Color = strokeColor;
  72. surf.Canvas.DrawPoints(PointMode.Points, ellipse!, paint);
  73. }
  74. else
  75. {
  76. if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
  77. {
  78. surf.Canvas.Save();
  79. surf.Canvas.ClipPath(innerPath!);
  80. surf.Canvas.DrawColor(fillColor, paint.BlendMode);
  81. surf.Canvas.Restore();
  82. }
  83. surf.Canvas.Save();
  84. surf.Canvas.ClipPath(outerPath!);
  85. surf.Canvas.ClipPath(innerPath!, ClipOperation.Difference);
  86. surf.Canvas.DrawColor(strokeColor, paint.BlendMode);
  87. surf.Canvas.Restore();
  88. }
  89. surf.Canvas.Restore();
  90. }
  91. public AffectedArea FindAffectedArea(VecI imageSize)
  92. {
  93. ShapeCorners corners = new((RectD)location);
  94. corners = corners.AsRotated(rotation, (VecD)location.Center);
  95. RectI bounds = (RectI)corners.AABBBounds;
  96. /*VecI shift = new VecI(Math.Max(0, -aabb.X), Math.Max(0, -aabb.Y));
  97. aabb = aabb.Offset(shift);*/
  98. var chunks = OperationHelper.FindChunksTouchingRectangle(bounds, ChunkyImage.FullChunkSize);
  99. if (fillColor.A == 0)
  100. {
  101. // TODO: Implement ellipse fill optimization for rotated ellipses
  102. /*
  103. chunks.ExceptWith(OperationHelper.FindChunksFullyInsideEllipse
  104. (location.Center, location.Width / 2.0 - strokeWidth * 2, location.Height / 2.0 - strokeWidth * 2, ChunkyImage.FullChunkSize));
  105. */
  106. }
  107. return new AffectedArea(chunks, bounds);
  108. }
  109. public IDrawOperation AsMirrored(double? verAxisX, double? horAxisY)
  110. {
  111. RectI newLocation = location;
  112. if (verAxisX is not null)
  113. newLocation = (RectI)newLocation.ReflectX((double)verAxisX).Round();
  114. if (horAxisY is not null)
  115. newLocation = (RectI)newLocation.ReflectY((double)horAxisY).Round();
  116. return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth, rotation, paint);
  117. }
  118. public void Dispose()
  119. {
  120. paint.Dispose();
  121. outerPath?.Dispose();
  122. innerPath?.Dispose();
  123. }
  124. }