PatternNode.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. using Drawie.Backend.Core;
  2. using Drawie.Backend.Core.ColorsImpl;
  3. using Drawie.Backend.Core.Mesh;
  4. using Drawie.Backend.Core.Numerics;
  5. using Drawie.Backend.Core.Surfaces;
  6. using Drawie.Backend.Core.Surfaces.PaintImpl;
  7. using Drawie.Backend.Core.Vector;
  8. using Drawie.Numerics;
  9. using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
  10. using PixiEditor.ChangeableDocument.Rendering;
  11. namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Image;
  12. [NodeInfo("Pattern")]
  13. public class PatternNode : RenderNode
  14. {
  15. public InputProperty<Texture?> Fill { get; }
  16. public InputProperty<Matrix3X3> FillMatrix { get; }
  17. public InputProperty<double> Spacing { get; }
  18. public InputProperty<ShapeVectorData?> Path { get; }
  19. public InputProperty<PatternAlignment> Alignment { get; }
  20. public InputProperty<PatternStretching> Stretching { get; }
  21. public PatternNode()
  22. {
  23. Fill = CreateInput<Texture>("Fill", "FILL", null);
  24. FillMatrix = CreateInput<Matrix3X3>("FillMatrix", "MATRIX", Matrix3X3.Identity);
  25. Spacing = CreateInput<double>("Spacing", "SPACING_LABEL", 0)
  26. .WithRules(x => x.Min(0d));
  27. Path = CreateInput<ShapeVectorData>("Path", "SHAPE", null);
  28. Alignment = CreateInput<PatternAlignment>("Alignment", "ALIGNMENT", PatternAlignment.Center);
  29. Stretching = CreateInput<PatternStretching>("Stretching", "STRETCHING", PatternStretching.StretchToFit);
  30. }
  31. protected override void OnPaint(RenderContext context, Canvas surface)
  32. {
  33. float spacing = (float)Spacing.Value;
  34. if (Fill.Value == null || Path.Value == null)
  35. return;
  36. if (spacing == 0)
  37. {
  38. spacing = Fill.Value.Size.X;
  39. }
  40. float distance = 0;
  41. using var path = Path.Value.ToPath(true);
  42. if (path == null)
  43. return;
  44. using Paint tilePaint = new Paint();
  45. using var snapshot = Fill.Value.DrawingSurface.Snapshot();
  46. using var shader = snapshot.ToShader(TileMode.Clamp, TileMode.Clamp, FillMatrix.Value);
  47. tilePaint.Shader = shader;
  48. while (distance < path.Length)
  49. {
  50. if (Stretching.Value == PatternStretching.PlaceAlong)
  51. {
  52. PlaceAlongPath(surface, snapshot, path, distance);
  53. }
  54. else if (Stretching.Value == PatternStretching.StretchToFit)
  55. {
  56. PlaceStretchToFit(surface, path, distance, spacing, tilePaint);
  57. }
  58. distance += spacing;
  59. }
  60. }
  61. private void PlaceAlongPath(Canvas surface, Drawie.Backend.Core.Surfaces.ImageData.Image image,
  62. VectorPath path, float distance)
  63. {
  64. var matrix = path.GetMatrixAtDistance(distance, false, PathMeasureMatrixMode.GetPositionAndTangent);
  65. if (matrix == null)
  66. return;
  67. if (Alignment.Value == PatternAlignment.Center)
  68. {
  69. matrix = matrix.Concat(Matrix3X3.CreateTranslation(-Fill.Value.Size.X / 2f, -Fill.Value.Size.Y / 2f));
  70. }
  71. else if (Alignment.Value == PatternAlignment.Outside)
  72. {
  73. matrix = matrix.Concat(Matrix3X3.CreateTranslation(0, -Fill.Value.Size.Y));
  74. }
  75. surface.Save();
  76. surface.SetMatrix(surface.TotalMatrix.Concat(matrix));
  77. surface.DrawImage(image, 0, 0);
  78. surface.Restore();
  79. }
  80. private void PlaceStretchToFit(Canvas surface, VectorPath path, float distance, float spacing,
  81. Paint tilePaint)
  82. {
  83. int texWidth = Fill.Value.Size.X;
  84. int texHeight = Fill.Value.Size.Y;
  85. // Iterate over each column of the texture (1px wide quads)
  86. for (int x = 0; x < texWidth; x++)
  87. {
  88. float u0 = (float)x / texWidth;
  89. float u1 = (float)(x + 1) / texWidth;
  90. float d0 = distance + u0 * spacing;
  91. float d1 = distance + u1 * spacing;
  92. var startSegment = path.GetPositionAndTangentAtDistance(d0, false);
  93. var endSegment = path.GetPositionAndTangentAtDistance(d1, false);
  94. var startNormal = new VecD(-startSegment.W, startSegment.Z).Normalize();
  95. var endNormal = new VecD(-endSegment.W, endSegment.Z).Normalize();
  96. float halfHeight = texHeight / 2f;
  97. VecD start = new VecD(startSegment.X, startSegment.Y);
  98. VecD end = new VecD(endSegment.X, endSegment.Y);
  99. if (Alignment.Value == PatternAlignment.Inside)
  100. {
  101. start += startNormal * halfHeight;
  102. end += endNormal * halfHeight;
  103. }
  104. else if (Alignment.Value == PatternAlignment.Outside)
  105. {
  106. start -= startNormal * halfHeight;
  107. end -= endNormal * halfHeight;
  108. }
  109. var v0 = start - startNormal * halfHeight;
  110. var v1 = start + startNormal * halfHeight;
  111. var v2 = end + endNormal * halfHeight;
  112. var v3 = end - endNormal * halfHeight;
  113. var texCoords = new VecF[] { new(x, texHeight), new(x, 0), new(x + 1, 0), new(x + 1, texHeight) };
  114. var verts = new VecF[] { (VecF)v0, (VecF)v1, (VecF)v2, (VecF)v3 };
  115. var indices = new ushort[] { 0, 1, 2, 0, 2, 3 };
  116. Color[] colors = { Colors.Transparent, Colors.Transparent, Colors.Transparent, Colors.Transparent };
  117. using var vertices = new Vertices(VertexMode.Triangles, verts, texCoords, colors, indices);
  118. surface.DrawVertices(vertices, BlendMode.SrcOver, tilePaint);
  119. }
  120. }
  121. public override Node CreateCopy()
  122. {
  123. return new PatternNode();
  124. }
  125. }
  126. public enum PatternAlignment
  127. {
  128. Center,
  129. Outside,
  130. Inside,
  131. }
  132. public enum PatternStretching
  133. {
  134. PlaceAlong,
  135. StretchToFit,
  136. }