LineBasedPen_UpdateableChange.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using ChunkyImageLib.Operations;
  2. using Drawie.Backend.Core.ColorsImpl;
  3. using Drawie.Backend.Core.Numerics;
  4. using Drawie.Backend.Core.Shaders;
  5. using Drawie.Backend.Core.Surfaces;
  6. using Drawie.Backend.Core.Surfaces.PaintImpl;
  7. using Drawie.Numerics;
  8. namespace PixiEditor.ChangeableDocument.Changes.Drawing;
  9. internal class LineBasedPen_UpdateableChange : UpdateableChange
  10. {
  11. private readonly Guid memberGuid;
  12. private readonly Color color;
  13. private int strokeWidth;
  14. private readonly bool erasing;
  15. private readonly bool drawOnMask;
  16. private readonly bool antiAliasing;
  17. private float hardness;
  18. private float spacing = 1;
  19. private readonly Paint srcPaint = new Paint() { BlendMode = BlendMode.Src };
  20. private CommittedChunkStorage? storedChunks;
  21. private readonly List<VecI> points = new();
  22. private int frame;
  23. private VecF lastPos;
  24. [GenerateUpdateableChangeActions]
  25. public LineBasedPen_UpdateableChange(Guid memberGuid, Color color, VecI pos, int strokeWidth, bool erasing,
  26. bool antiAliasing,
  27. float hardness,
  28. float spacing,
  29. bool drawOnMask, int frame)
  30. {
  31. this.memberGuid = memberGuid;
  32. this.color = color;
  33. this.strokeWidth = strokeWidth;
  34. this.erasing = erasing;
  35. this.antiAliasing = antiAliasing;
  36. this.drawOnMask = drawOnMask;
  37. this.hardness = hardness;
  38. this.spacing = spacing;
  39. points.Add(pos);
  40. this.frame = frame;
  41. if (this.antiAliasing && !erasing)
  42. {
  43. srcPaint.BlendMode = BlendMode.SrcOver;
  44. }
  45. else if (erasing)
  46. {
  47. srcPaint.BlendMode = BlendMode.DstOut;
  48. }
  49. }
  50. [UpdateChangeMethod]
  51. public void Update(VecI pos, int strokeWidth)
  52. {
  53. points.Add(pos);
  54. this.strokeWidth = strokeWidth;
  55. }
  56. public override bool InitializeAndValidate(Document target)
  57. {
  58. if (!DrawingChangeHelper.IsValidForDrawing(target, memberGuid, drawOnMask))
  59. return false;
  60. if (strokeWidth < 1)
  61. return false;
  62. var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask, frame);
  63. if (!erasing)
  64. image.SetBlendMode(BlendMode.SrcOver);
  65. DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
  66. srcPaint.IsAntiAliased = antiAliasing;
  67. return true;
  68. }
  69. public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
  70. {
  71. var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask, frame);
  72. var (from, to) = points.Count > 1 ? (points[^2], points[^1]) : (points[0], points[0]);
  73. int opCount = image.QueueLength;
  74. var bresenham = BresenhamLineHelper.GetBresenhamLine(from, to);
  75. float spacingPixels = strokeWidth * spacing;
  76. foreach (var point in bresenham)
  77. {
  78. if (points.Count > 1 && VecF.Distance(lastPos, point) < spacingPixels)
  79. continue;
  80. lastPos = point;
  81. var rect = new RectI(point - new VecI(strokeWidth / 2), new VecI(strokeWidth));
  82. if (antiAliasing)
  83. {
  84. ApplySoftnessGradient((VecD)point);
  85. }
  86. image.EnqueueDrawEllipse(rect, color, color, 0, 0, antiAliasing, srcPaint);
  87. }
  88. var affChunks = image.FindAffectedArea(opCount);
  89. return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affChunks, drawOnMask);
  90. }
  91. private void FastforwardEnqueueDrawLines(ChunkyImage targetImage)
  92. {
  93. if (points.Count == 1)
  94. {
  95. var rect = new RectI(points[0] - new VecI(strokeWidth / 2), new VecI(strokeWidth));
  96. targetImage.EnqueueDrawEllipse(rect, color, color, 1, 0, antiAliasing, srcPaint);
  97. return;
  98. }
  99. VecF lastPos = points[0];
  100. float spacingInPixels = strokeWidth * this.spacing;
  101. for (int i = 0; i < points.Count; i++)
  102. {
  103. if (i > 0 && VecF.Distance(lastPos, points[i]) < spacingInPixels)
  104. continue;
  105. lastPos = points[i];
  106. var rect = new RectI(points[i] - new VecI(strokeWidth / 2), new VecI(strokeWidth));
  107. if (antiAliasing)
  108. {
  109. ApplySoftnessGradient(points[i]);
  110. }
  111. targetImage.EnqueueDrawEllipse(rect, color, color, 0, 0, antiAliasing, srcPaint);
  112. }
  113. }
  114. private void ApplySoftnessGradient(VecD pos)
  115. {
  116. srcPaint.Shader?.Dispose();
  117. float radius = strokeWidth / 2f;
  118. radius = MathF.Max(1, radius);
  119. srcPaint.Shader = Shader.CreateRadialGradient(
  120. pos, radius, [color, color.WithAlpha(0)],
  121. [hardness - 0.03f, 1f], ShaderTileMode.Clamp);
  122. }
  123. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
  124. out bool ignoreInUndo)
  125. {
  126. if (storedChunks is not null)
  127. throw new InvalidOperationException("Trying to save chunks while there are saved chunks already");
  128. var image = DrawingChangeHelper.GetTargetImageOrThrow(target, memberGuid, drawOnMask, frame);
  129. ignoreInUndo = false;
  130. if (firstApply)
  131. {
  132. var affArea = image.FindAffectedArea();
  133. storedChunks = new CommittedChunkStorage(image, affArea.Chunks);
  134. image.CommitChanges();
  135. return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask);
  136. }
  137. else
  138. {
  139. if (!erasing)
  140. image.SetBlendMode(BlendMode.SrcOver);
  141. DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, image, memberGuid, drawOnMask);
  142. FastforwardEnqueueDrawLines(image);
  143. var affArea = image.FindAffectedArea();
  144. storedChunks = new CommittedChunkStorage(image, affArea.Chunks);
  145. image.CommitChanges();
  146. return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affArea, drawOnMask);
  147. }
  148. }
  149. public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
  150. {
  151. var affected =
  152. DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(target, memberGuid, drawOnMask, frame,
  153. ref storedChunks);
  154. return DrawingChangeHelper.CreateAreaChangeInfo(memberGuid, affected, drawOnMask);
  155. }
  156. public override void Dispose()
  157. {
  158. storedChunks?.Dispose();
  159. }
  160. }