Scene.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. using System.Diagnostics;
  2. using System.Linq;
  3. using Avalonia;
  4. using Avalonia.Controls;
  5. using Avalonia.Media;
  6. using Avalonia.OpenGL;
  7. using Avalonia.OpenGL.Controls;
  8. using Avalonia.Rendering.SceneGraph;
  9. using Avalonia.Skia;
  10. using ChunkyImageLib;
  11. using ChunkyImageLib.DataHolders;
  12. using PixiEditor.AvaloniaUI.ViewModels.Document;
  13. using PixiEditor.DrawingApi.Core.Numerics;
  14. using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
  15. namespace PixiEditor.AvaloniaUI.Views.Visuals;
  16. internal class Scene : Control
  17. {
  18. public static readonly StyledProperty<Surface> SurfaceProperty = AvaloniaProperty.Register<SurfaceControl, Surface>(
  19. nameof(Surface));
  20. public static readonly StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<Scene, double>(
  21. nameof(Scale), 1);
  22. public static readonly StyledProperty<VecI> ContentPositionProperty = AvaloniaProperty.Register<Scene, VecI>(
  23. nameof(ContentPosition));
  24. public static readonly StyledProperty<DocumentViewModel> DocumentProperty = AvaloniaProperty.Register<Scene, DocumentViewModel>(
  25. nameof(Document));
  26. public static readonly StyledProperty<double> AngleProperty = AvaloniaProperty.Register<Scene, double>(
  27. nameof(Angle), 0);
  28. public static readonly StyledProperty<bool> FlipXProperty = AvaloniaProperty.Register<Scene, bool>(
  29. nameof(FlipX), false);
  30. public static readonly StyledProperty<bool> FlipYProperty = AvaloniaProperty.Register<Scene, bool>(
  31. nameof(FlipY), false);
  32. public double Angle
  33. {
  34. get => GetValue(AngleProperty);
  35. set => SetValue(AngleProperty, value);
  36. }
  37. public DocumentViewModel Document
  38. {
  39. get => GetValue(DocumentProperty);
  40. set => SetValue(DocumentProperty, value);
  41. }
  42. public VecI ContentPosition
  43. {
  44. get => GetValue(ContentPositionProperty);
  45. set => SetValue(ContentPositionProperty, value);
  46. }
  47. public double Scale
  48. {
  49. get => GetValue(ScaleProperty);
  50. set => SetValue(ScaleProperty, value);
  51. }
  52. public Surface Surface
  53. {
  54. get => GetValue(SurfaceProperty);
  55. set => SetValue(SurfaceProperty, value);
  56. }
  57. public Rect FinalBounds => Bounds;
  58. public bool FlipX
  59. {
  60. get { return (bool)GetValue(FlipXProperty); }
  61. set { SetValue(FlipXProperty, value); }
  62. }
  63. public bool FlipY
  64. {
  65. get { return (bool)GetValue(FlipYProperty); }
  66. set { SetValue(FlipYProperty, value); }
  67. }
  68. static Scene()
  69. {
  70. AffectsRender<Scene>(BoundsProperty, WidthProperty, HeightProperty, ScaleProperty, AngleProperty, FlipXProperty, FlipYProperty, ContentPositionProperty, DocumentProperty, SurfaceProperty);
  71. BoundsProperty.Changed.AddClassHandler<Scene>(BoundsChanged);
  72. FlipXProperty.Changed.AddClassHandler<Scene>(RequestRendering);
  73. FlipYProperty.Changed.AddClassHandler<Scene>(RequestRendering);
  74. }
  75. public Scene()
  76. {
  77. ClipToBounds = true;
  78. }
  79. public override void Render(DrawingContext context)
  80. {
  81. if (Surface == null || Document == null) return;
  82. var operation = new DrawSceneOperation(Surface, Document, ContentPosition, Scale, Angle, FlipX, FlipY, Bounds);
  83. context.Custom(operation);
  84. }
  85. private static void BoundsChanged(Scene sender, AvaloniaPropertyChangedEventArgs e)
  86. {
  87. sender.InvalidateVisual();
  88. }
  89. private static void RequestRendering(Scene sender, AvaloniaPropertyChangedEventArgs e)
  90. {
  91. sender.InvalidateVisual();
  92. }
  93. }
  94. internal class DrawSceneOperation : SkiaDrawOperation
  95. {
  96. public Surface Surface { get; set; }
  97. public DocumentViewModel Document { get; set; }
  98. public VecI ContentPosition { get; set; }
  99. public double Scale { get; set; }
  100. public double Angle { get; set; }
  101. public bool FlipX { get; set; }
  102. public bool FlipY { get; set; }
  103. private SKPaint _paint = new SKPaint();
  104. public DrawSceneOperation(Surface surface, DocumentViewModel document, VecI contentPosition, double scale, double angle, bool flipX, bool flipY, Rect bounds) : base(bounds)
  105. {
  106. Surface = surface;
  107. Document = document;
  108. ContentPosition = contentPosition;
  109. Scale = scale;
  110. Angle = angle;
  111. FlipX = flipX;
  112. FlipY = flipY;
  113. }
  114. public override void Render(ISkiaSharpApiLease lease)
  115. {
  116. if (Surface == null || Document == null) return;
  117. SKCanvas canvas = lease.SkCanvas;
  118. canvas.Save();
  119. float finalScale = CalculateFinalScale();
  120. RectI surfaceRectToRender = FindRectToRender(finalScale);
  121. if (surfaceRectToRender.IsZeroOrNegativeArea)
  122. {
  123. canvas.Restore();
  124. canvas.Flush();
  125. return;
  126. }
  127. canvas.Scale(finalScale, finalScale, ContentPosition.X, ContentPosition.Y);
  128. float angle = (float)Angle;
  129. if (FlipX)
  130. {
  131. angle = 360 - angle;
  132. }
  133. if (FlipY)
  134. {
  135. angle = 360 - angle;
  136. }
  137. canvas.RotateDegrees(angle, ContentPosition.X, ContentPosition.Y);
  138. canvas.Scale(FlipX ? -1 : 1, FlipY ? -1 : 1, ContentPosition.X, ContentPosition.Y);
  139. canvas.Translate(ContentPosition.X, ContentPosition.Y);
  140. using Image snapshot = Surface.DrawingSurface.Snapshot(surfaceRectToRender);
  141. canvas.DrawImage((SKImage)snapshot.Native, surfaceRectToRender.X, surfaceRectToRender.Y, _paint);
  142. canvas.Restore();
  143. canvas.Flush();
  144. }
  145. private RectI FindRectToRender(float finalScale)
  146. {
  147. ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);
  148. RectI surfaceBoundsInViewportSpace = (RectI)surfaceInViewportSpace.AABBBounds.RoundOutwards();
  149. RectI viewportBoundsInViewportSpace = (RectI)(new RectD(Bounds.X, Bounds.Y, Bounds.Width, Bounds.Height)).RoundOutwards();
  150. RectI firstIntersectionInViewportSpace = surfaceBoundsInViewportSpace.Intersect(viewportBoundsInViewportSpace);
  151. ShapeCorners firstIntersectionInSurfaceSpace = ViewportToSurface(firstIntersectionInViewportSpace, finalScale);
  152. RectI firstIntersectionBoundsInSurfaceSpace = (RectI)firstIntersectionInSurfaceSpace.AABBBounds.RoundOutwards();
  153. ShapeCorners viewportInSurfaceSpace = ViewportToSurface(viewportBoundsInViewportSpace, finalScale);
  154. RectD viewportBoundsInSurfaceSpace = viewportInSurfaceSpace.AABBBounds;
  155. RectD surfaceBoundsInSurfaceSpace = new(VecD.Zero, Surface.Size);
  156. RectI secondIntersectionInSurfaceSpace = (RectI)viewportBoundsInSurfaceSpace.Intersect(surfaceBoundsInSurfaceSpace).RoundOutwards();
  157. //Inflate makes sure rounding doesn't cut any pixels.
  158. RectI surfaceRectToRender = firstIntersectionBoundsInSurfaceSpace.Intersect(secondIntersectionInSurfaceSpace).Inflate(1);
  159. return surfaceRectToRender.Intersect(new RectI(VecI.Zero, Surface.Size)); // Clamp to surface size
  160. }
  161. private void DrawDebugRect(SKCanvas canvas, RectD rect)
  162. {
  163. canvas.DrawLine((float)rect.X, (float)rect.Y, (float)rect.Right, (float)rect.Y, _paint);
  164. canvas.DrawLine((float)rect.Right, (float)rect.Y, (float)rect.Right, (float)rect.Bottom, _paint);
  165. canvas.DrawLine((float)rect.Right, (float)rect.Bottom, (float)rect.X, (float)rect.Bottom, _paint);
  166. canvas.DrawLine((float)rect.X, (float)rect.Bottom, (float)rect.X, (float)rect.Y, _paint);
  167. }
  168. private ShapeCorners ViewportToSurface(RectI viewportRect, float scale)
  169. {
  170. return new ShapeCorners()
  171. {
  172. TopLeft = ViewportToSurface(viewportRect.TopLeft, scale),
  173. TopRight = ViewportToSurface(viewportRect.TopRight, scale),
  174. BottomLeft = ViewportToSurface(viewportRect.BottomLeft, scale),
  175. BottomRight = ViewportToSurface(viewportRect.BottomRight, scale),
  176. };
  177. }
  178. private ShapeCorners SurfaceToViewport(RectI viewportRect, float scale)
  179. {
  180. return new ShapeCorners()
  181. {
  182. TopLeft = SurfaceToViewport(viewportRect.TopLeft, scale),
  183. TopRight = SurfaceToViewport(viewportRect.TopRight, scale),
  184. BottomLeft = SurfaceToViewport(viewportRect.BottomLeft, scale),
  185. BottomRight = SurfaceToViewport(viewportRect.BottomRight, scale),
  186. };
  187. }
  188. private float CalculateFinalScale()
  189. {
  190. var scaleUniform = CalculateResolutionScale();
  191. float scale = (float)Scale * scaleUniform;
  192. return scale;
  193. }
  194. private float CalculateResolutionScale()
  195. {
  196. float scaleX = (float)Document.Width / Surface.Size.X;
  197. float scaleY = (float)Document.Height / Surface.Size.Y;
  198. var scaleUniform = Math.Min(scaleX, scaleY);
  199. return scaleUniform;
  200. }
  201. private VecD SurfaceToViewport(VecI surfacePoint, float scale)
  202. {
  203. VecD unscaledPoint = surfacePoint * scale;
  204. float angle = (float)Angle;
  205. if (FlipX)
  206. {
  207. unscaledPoint.X = -unscaledPoint.X;
  208. angle = 360 - angle;
  209. }
  210. if (FlipY)
  211. {
  212. unscaledPoint.Y = -unscaledPoint.Y;
  213. angle = 360 - angle;
  214. }
  215. VecD offseted = unscaledPoint + ContentPosition;
  216. float angleRadians = (float)(angle * Math.PI / 180);
  217. VecD rotated = offseted.Rotate(angleRadians, ContentPosition);
  218. return rotated;
  219. }
  220. private VecI ViewportToSurface(VecD viewportPoint, float scale)
  221. {
  222. float angle = (float)Angle;
  223. if (FlipX)
  224. {
  225. angle = 360 - angle;
  226. }
  227. if (FlipY)
  228. {
  229. angle = 360 - angle;
  230. }
  231. float angleRadians = (float)(angle * Math.PI / 180);
  232. VecD rotatedViewportPoint = (viewportPoint).Rotate(-angleRadians, ContentPosition);
  233. VecD unscaledPoint = rotatedViewportPoint - ContentPosition;
  234. if (FlipX)
  235. unscaledPoint.X = -unscaledPoint.X;
  236. if (FlipY)
  237. unscaledPoint.Y = -unscaledPoint.Y;
  238. VecI pos = new VecI(
  239. (int)Math.Round(unscaledPoint.X / scale),
  240. (int)Math.Round(unscaledPoint.Y / scale));
  241. return pos;
  242. }
  243. public override bool Equals(ICustomDrawOperation? other)
  244. {
  245. return false;
  246. }
  247. }