Browse Source

Improved SurfaceControl

Krzysztof Krysiński 1 year ago
parent
commit
8f817110d8

+ 15 - 9
src/ChunkyImageLib/Surface.cs

@@ -16,7 +16,8 @@ public class Surface : IDisposable
     public DrawingSurface DrawingSurface { get; }
     public int BytesPerPixel { get; }
     public VecI Size { get; }
-    public RectI DirtyRect { get; private set; }
+
+    public event SurfaceChangedEventHandler? Changed;
 
     private Paint drawingPaint = new Paint() { BlendMode = BlendMode.Src };
     private Paint nearestNeighborReplacingPaint = new() { BlendMode = BlendMode.Src, FilterQuality = FilterQuality.None };
@@ -97,6 +98,8 @@ public class Surface : IDisposable
             using DrawingSurface surface = DrawingSurface.Create(map);
             surface.Draw(DrawingSurface.Canvas, 0, 0, drawingPaint);
         }
+
+        DrawingSurfaceChanged(new RectD(0, 0, size.X, size.Y));
     }
 
     public Surface Resize(VecI newSize, ResizeMethod resizeMethod)
@@ -150,6 +153,7 @@ public class Surface : IDisposable
     {
         drawingPaint.Color = color;
         DrawingSurface.Canvas.DrawPixel(pos.X, pos.Y, drawingPaint);
+        DrawingSurfaceChanged(new RectD(pos.X, pos.Y, 1, 1));
     }
 
     public unsafe bool IsFullyTransparent()
@@ -181,7 +185,8 @@ public class Surface : IDisposable
 
     private DrawingSurface CreateDrawingSurface()
     {
-        var surface = PixiEditor.DrawingApi.Core.Surface.DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
+        var surface = DrawingSurface.Create(new ImageInfo(Size.X, Size.Y, ColorType.RgbaF16, AlphaType.Premul, ColorSpace.CreateSrgb()), PixelBuffer);
+        surface.Changed += DrawingSurfaceChanged;
         if (surface is null)
             throw new InvalidOperationException($"Could not create surface (Size:{Size})");
         return surface;
@@ -199,6 +204,8 @@ public class Surface : IDisposable
     {
         if (disposed)
             return;
+
+        DrawingSurface.Changed -= DrawingSurfaceChanged;
         disposed = true;
         drawingPaint.Dispose();
         nearestNeighborReplacingPaint.Dispose();
@@ -206,20 +213,19 @@ public class Surface : IDisposable
         GC.SuppressFinalize(this);
     }
 
-    ~Surface()
+    public void AddDirtyRect(RectI dirtyRect)
     {
-        Marshal.FreeHGlobal(PixelBuffer);
+        DrawingSurfaceChanged(new RectD(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
     }
 
-    public void AddDirtyRect(RectI dirtyRect)
+    private void DrawingSurfaceChanged(RectD? changedRect)
     {
-        // TODO: yeah well this is probably wrong lol, since the name even says "Add" and not "Set"
-        DirtyRect = dirtyRect;
+        Changed?.Invoke(changedRect);
     }
 
-    public void ClearDirtyRects()
+    ~Surface()
     {
-        DirtyRect = default;
+        Marshal.FreeHGlobal(PixelBuffer);
     }
 }
 

+ 1 - 9
src/PixiEditor.AvaloniaUI/Models/DocumentModels/ActionAccumulator.cs

@@ -96,7 +96,7 @@ internal class ActionAccumulator
 
             if (undoBoundaryPassed)
             {
-                ClearDirtyRects();
+                //ClearDirtyRects();
             }
 
             // add dirty rectangles
@@ -116,14 +116,6 @@ internal class ActionAccumulator
         executing = false;
     }
 
-    private void ClearDirtyRects()
-    {
-        foreach (var surface in document.Surfaces)
-        {
-            surface.Value.ClearDirtyRects();
-        }
-    }
-
     private bool AreAllPassthrough(List<IAction> actions)
     {
         foreach (var action in actions)

+ 1 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditorPopupTemplate.axaml

@@ -35,6 +35,7 @@
                                 <behaviours:ClearFocusOnClickBehavior/>
                             </Interaction.Behaviors>
 
+                            <!--TODO: FocusAdorner seems not to work-->
                             <ContentPresenter DockPanel.Dock="Bottom"
                                               Name="PART_ContentPresenter"
                                               Margin="{TemplateBinding Padding}"

+ 13 - 2
src/PixiEditor.AvaloniaUI/ViewModels/Document/StructureMemberViewModel.cs

@@ -144,9 +144,20 @@ internal abstract class StructureMemberViewModel : ObservableObject, IStructureM
         set => SetProperty(ref selection, value);
     }
 
-    public Surface? PreviewSurface { get; set; }
+    private Surface? previewSurface;
+    private Surface? maskPreviewSurface;
 
-    public Surface? MaskPreviewSurface { get; set; }
+    public Surface? PreviewSurface
+    {
+        get => previewSurface;
+        set => SetProperty(ref previewSurface, value);
+    }
+
+    public Surface? MaskPreviewSurface
+    {
+        get => maskPreviewSurface;
+        set => SetProperty(ref maskPreviewSurface, value);
+    }
 
     IDocument IStructureMemberHandler.Document => Document;
 

+ 10 - 10
src/PixiEditor.AvaloniaUI/Views/Dialogs/NewFilePopup.axaml

@@ -13,15 +13,7 @@
         CanMinimize="False"
         Name="popup"
         Title="CREATE_NEW_IMAGE">
-    <DockPanel Background="{DynamicResource ThemeBackgroundBrush}" Focusable="True">
-        <Button DockPanel.Dock="Bottom" Margin="0,15,0,15" HorizontalAlignment="Center"
-                IsDefault="True" ui:Translator.Key="CREATE" x:Name="createButton"
-                Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}">
-            <Button.CommandParameter>
-                <system:Boolean>True</system:Boolean>
-            </Button.CommandParameter>
-        </Button>
-
+    <StackPanel Background="{DynamicResource ThemeBackgroundBrush}" Focusable="True" Orientation="Vertical">
         <input:SizePicker HorizontalAlignment="Center" MinWidth="230" Height="125" Margin="15,30,15,0"
                           PreserveAspectRatio="False"
                           Background="{DynamicResource ThemeBackgroundBrush1}"
@@ -29,5 +21,13 @@
                           ChosenHeight="{Binding FileHeight, Mode=TwoWay, ElementName=popup}"
                           ChosenWidth="{Binding FileWidth, Mode=TwoWay, ElementName=popup}"
                           x:Name="sizePicker"/>
-    </DockPanel>
+
+        <Button DockPanel.Dock="Bottom" Margin="0,15,0,15" HorizontalAlignment="Center"
+                IsDefault="True" ui:Translator.Key="CREATE" x:Name="createButton" Focusable="True"
+                Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}">
+            <Button.CommandParameter>
+                <system:Boolean>True</system:Boolean>
+            </Button.CommandParameter>
+        </Button>
+    </StackPanel>
 </dialogs:PixiEditorPopup>

+ 15 - 15
src/PixiEditor.AvaloniaUI/Views/Input/SizeInput.axaml.cs

@@ -31,21 +31,6 @@ internal partial class SizeInput : UserControl
     public static readonly StyledProperty<Action> OnScrollActionProperty =
         AvaloniaProperty.Register<SizeInput, Action>(nameof(OnScrollAction));
 
-    static SizeInput()
-    {
-        SizeProperty.Changed.Subscribe(InputSizeChanged);
-    }
-
-    public SizeInput()
-    {
-        InitializeComponent();
-    }
-
-    private void SizeInput_GotKeyboardFocus(object sender, GotFocusEventArgs e)
-    {
-        textBox.Focus();
-    }
-
     public int Size
     {
         get => (int)GetValue(SizeProperty);
@@ -64,6 +49,21 @@ internal partial class SizeInput : UserControl
         set => SetValue(BehaveLikeSmallEmbeddedFieldProperty, value);
     }
 
+    static SizeInput()
+    {
+        SizeProperty.Changed.Subscribe(InputSizeChanged);
+    }
+
+    public SizeInput()
+    {
+        InitializeComponent();
+    }
+
+    protected override void OnGotFocus(GotFocusEventArgs e)
+    {
+        FocusAndSelect();
+    }
+
     public void FocusAndSelect()
     {
         textBox.Focus();

+ 43 - 12
src/PixiEditor.AvaloniaUI/Views/Visuals/SurfaceControl.cs

@@ -2,12 +2,9 @@
 using Avalonia.Media;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL.Controls;
-using Avalonia.Platform;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Skia;
+using Avalonia.Threading;
 using ChunkyImageLib;
-using PixiEditor.DrawingApi.Skia;
-using Point = Avalonia.Point;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Views.Visuals;
 
@@ -34,13 +31,12 @@ public class SurfaceControl : OpenGlControlBase
     private SKSurface _workingSurface;
     private SKPaint _paint = new SKPaint() { BlendMode = SKBlendMode.Src };
     private GRContext? gr;
+    private RectI? nextDirtyRect;
 
     static SurfaceControl()
     {
-        AffectsRender<SurfaceControl>(StretchProperty);
+        AffectsRender<SurfaceControl>(StretchProperty, SurfaceProperty);
         BoundsProperty.Changed.AddClassHandler<SurfaceControl>(BoundsChanged);
-        WidthProperty.Changed.AddClassHandler<SurfaceControl>(BoundsChanged);
-        HeightProperty.Changed.AddClassHandler<SurfaceControl>(BoundsChanged);
         SurfaceProperty.Changed.AddClassHandler<SurfaceControl>(Rerender);
         StretchProperty.Changed.AddClassHandler<SurfaceControl>(Rerender);
     }
@@ -68,7 +64,6 @@ public class SurfaceControl : OpenGlControlBase
 
     protected override void OnOpenGlRender(GlInterface gl, int fb)
     {
-        // TODO: draw only when needed
         if (Surface == null)
         {
             return;
@@ -80,12 +75,21 @@ public class SurfaceControl : OpenGlControlBase
         ScaleCanvas(canvas);
         canvas.Clear(SKColors.Transparent);
 
+        //TODO: Implement dirty rect rendering
+        /*if (nextDirtyRect.HasValue)
+        {
+            using SKImage snapshot = (SKImage)Surface.DrawingSurface.Snapshot(nextDirtyRect.Value).Native;
+            canvas.DrawImage(snapshot, nextDirtyRect.Value.X, nextDirtyRect.Value.Y, _paint);
+        }
+        else
+        {
+            canvas.DrawSurface((SKSurface)Surface.DrawingSurface.Native, new SKPoint(0, 0), _paint);
+        }*/
+
         canvas.DrawSurface((SKSurface)Surface.DrawingSurface.Native, new SKPoint(0, 0), _paint);
 
         canvas.Restore();
-
         canvas.Flush();
-        RequestNextFrameRendering();
     }
 
     private void ScaleCanvas(SKCanvas canvas)
@@ -116,16 +120,43 @@ public class SurfaceControl : OpenGlControlBase
         }
     }
 
+    private void SurfaceChanged(RectD? changedRect)
+    {
+        if (changedRect.HasValue)
+        {
+            var rect = changedRect.Value;
+            var rectI = new RectI((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
+            nextDirtyRect = nextDirtyRect?.Union(rectI) ?? rectI;
+        }
+        else
+        {
+            nextDirtyRect = null;
+        }
+
+        Dispatcher.UIThread.Post(RequestNextFrameRendering, DispatcherPriority.Render);
+    }
+
     private static void BoundsChanged(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
     {
         if (e.NewValue is Rect bounds)
         {
             sender.CreateWorkingSurface();
         }
+
+        Dispatcher.UIThread.Post(sender.RequestNextFrameRendering, DispatcherPriority.Render);
     }
 
     private static void Rerender(SurfaceControl sender, AvaloniaPropertyChangedEventArgs e)
     {
-        sender.RequestNextFrameRendering();
+        if (e.OldValue is Surface oldSurface)
+        {
+            oldSurface.Changed -= sender.SurfaceChanged;
+        }
+        if (e.NewValue is Surface newSurface)
+        {
+            newSurface.Changed += sender.SurfaceChanged;
+        }
+
+        Dispatcher.UIThread.Post(sender.RequestNextFrameRendering, DispatcherPriority.Render);
     }
 }

+ 4 - 3
src/PixiEditor.AvaloniaUI/Views/Windows/HelloTherePopup.axaml

@@ -15,6 +15,7 @@
         xmlns:dialogs="clr-namespace:PixiEditor.AvaloniaUI.Views.Dialogs"
         xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
         xmlns:skiaSharp="clr-namespace:SkiaSharp;assembly=SkiaSharp"
+        xmlns:ui1="clr-namespace:PixiEditor.AvaloniaUI.Helpers.UI"
         mc:Ignorable="d"
         Title="Hello there!" Height="662" Width="982" MinHeight="500" MinWidth="500"
         Loaded="HelloTherePopup_OnLoaded">
@@ -123,12 +124,12 @@
                                                         Stretch="Uniform"
                                                         x:Name="image"
                                                         Margin="20">
-                                                        <!--<RenderOptions.BitmapInterpolationMode> TODO: Fix
+                                                        <ui1:RenderOptionsBindable.BitmapInterpolationMode>
                                                             <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
-                                                                <Binding Path="PreviewBitmap.PixelSize.Width"/>
+                                                                <Binding Path="PreviewBitmap.Size.X"/>
                                                                 <Binding ElementName="image" Path="Width"/>
                                                             </MultiBinding>
-                                                        </RenderOptions.BitmapInterpolationMode>-->
+                                                        </ui1:RenderOptionsBindable.BitmapInterpolationMode>
                                                     </visuals:SurfaceControl>
                                                     <Border Grid.Row="1" Height="8" Width="8" x:Name="extensionBorder" Margin="5"
                                                             Background="{Binding FileExtension, Converter={converters:FileExtensionToColorConverter}}" 

+ 27 - 0
src/PixiEditor.DrawingApi.Core/Numerics/RectD.cs

@@ -1,4 +1,5 @@
 using System;
+using PixiEditor.DrawingApi.Core.Surface;
 
 namespace PixiEditor.DrawingApi.Core.Numerics;
 public struct RectD : IEquatable<RectD>
@@ -158,6 +159,32 @@ public struct RectD : IEquatable<RectD>
         return new RectD(min, max - min);
     }
 
+    public static RectD? FromPoints(Point[] points)
+    {
+        if (points.Length == 0)
+            return null;
+
+        double minX, minY, maxX, maxY;
+        minY = double.MaxValue;
+        minX = double.MaxValue;
+        maxY = double.MinValue;
+        maxX = double.MinValue;
+
+        foreach (Point point in points)
+        {
+            if (point.X < minX)
+                minX = point.X;
+            if (point.X > maxX)
+                maxX = point.X;
+            if (point.Y < minY)
+                minY = point.Y;
+            if (point.Y > maxY)
+                maxY = point.Y;
+        }
+
+        return FromTwoPoints(new VecD(minX, minY), new VecD(maxX, maxY));
+    }
+
     /// <summary>
     /// Converts rectangles with negative dimensions into a normal one
     /// </summary>

+ 32 - 12
src/PixiEditor.DrawingApi.Core/Surface/Canvas.cs

@@ -8,22 +8,30 @@ using PixiEditor.DrawingApi.Core.Surface.Vector;
 
 namespace PixiEditor.DrawingApi.Core.Surface
 {
+    public delegate void SurfaceChangedEventHandler(RectD? changedRect);
     public class Canvas : NativeObject
     {
 
         public override object Native => DrawingBackendApi.Current.CanvasImplementation.GetNativeCanvas(ObjectPointer);
+        public event SurfaceChangedEventHandler? Changed;
 
         public Canvas(IntPtr objPtr) : base(objPtr)
         {
         }
 
         public void DrawPixel(VecI position, Paint drawingPaint) => DrawPixel(position.X, position.Y, drawingPaint);
-        public void DrawPixel(int posX, int posY, Paint drawingPaint) => 
+        public void DrawPixel(int posX, int posY, Paint drawingPaint)
+        {
             DrawingBackendApi.Current.CanvasImplementation.DrawPixel(ObjectPointer, posX, posY, drawingPaint);
+            Changed?.Invoke(new RectD(posX, posY, 1, 1));
+        }
+
+        public void DrawSurface(DrawingSurface original, int x, int y, Paint? paint)
+        {
+            DrawingBackendApi.Current.CanvasImplementation.DrawSurface(ObjectPointer, original, x, y, paint);
+            Changed?.Invoke(null);
+        }
 
-        public void DrawSurface(DrawingSurface original, int x, int y, Paint? paint) 
-            => DrawingBackendApi.Current.CanvasImplementation.DrawSurface(ObjectPointer, original, x, y, paint);
-        
         public void DrawSurface(DrawingSurface original, int x, int y) => DrawSurface(original, x, y, null);
         
         public void DrawSurface(DrawingSurface surfaceToDraw, VecI size, Paint paint)
@@ -46,16 +54,19 @@ namespace PixiEditor.DrawingApi.Core.Surface
             DrawingBackendApi.Current.CanvasImplementation.Restore(ObjectPointer);
         }
         
-        public void Scale(float s) => DrawingBackendApi.Current.CanvasImplementation.Scale(ObjectPointer, s, s);
+        public void Scale(float s) => Scale(s, s);
 
-        /// <param name="sx">The amount to scale in the x-direction.</param>
-        /// <param name="sy">The amount to scale in the y-direction.</param>
+        /// <param name="size">The amount to scale.</param>
         /// <summary>Pre-concatenates the current matrix with the specified scale.</summary>
-        public void Scale(float sx, float sy) => DrawingBackendApi.Current.CanvasImplementation.Scale(ObjectPointer, sx, sy);
+        public void Scale(Point size) => Scale(size.X, size.Y);
 
-        /// <param name="size">The amount to scale.</param>
+        /// <param name="sx">The amount to scale in the x-direction.</param>
+        /// <param name="sy">The amount to scale in the y-direction.</param>
         /// <summary>Pre-concatenates the current matrix with the specified scale.</summary>
-        public void Scale(Point size) => DrawingBackendApi.Current.CanvasImplementation.Scale(ObjectPointer, size.X, size.Y);
+        public void Scale(float sx, float sy)
+        {
+            DrawingBackendApi.Current.CanvasImplementation.Scale(ObjectPointer, sx, sy);
+        }
 
         /// <param name="sx">The amount to scale in the x-direction.</param>
         /// <param name="sy">The amount to scale in the y-direction.</param>
@@ -69,31 +80,35 @@ namespace PixiEditor.DrawingApi.Core.Surface
             Translate(-px, -py);
         }
 
+        public void Translate(VecD vector) => Translate((float)vector.X, (float)vector.Y);
+
         public void Translate(float translationX, float translationY)
         {
             DrawingBackendApi.Current.CanvasImplementation.Translate(ObjectPointer, translationX, translationY);
         }
-        
-        public void Translate(VecD vector) => Translate((float)vector.X, (float)vector.Y);
 
         public void DrawPath(VectorPath path, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawPath(ObjectPointer, path, paint);
+            Changed?.Invoke(path.Bounds);
         }
         
         public void DrawPoint(VecI pos, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawPoint(ObjectPointer, pos, paint);
+            Changed?.Invoke(new RectD(pos.X, pos.Y, 1, 1));
         }
 
         public void DrawPoints(PointMode pointMode, Point[] points, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawPoints(ObjectPointer, pointMode, points, paint);
+            Changed?.Invoke(RectD.FromPoints(points));
         }
 
         public void DrawRect(int x, int y, int width, int height, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawRect(ObjectPointer, x, y, width, height, paint);
+            Changed?.Invoke(new RectD(x, y, width, height));
         }
         
         public void DrawRect(RectI rect, Paint paint) => DrawRect(rect.X, rect.Y, rect.Width, rect.Height, paint);
@@ -116,16 +131,19 @@ namespace PixiEditor.DrawingApi.Core.Surface
         public void Clear()
         {
             DrawingBackendApi.Current.CanvasImplementation.Clear(ObjectPointer);
+            Changed?.Invoke(null);
         }
         
         public void Clear(Color color)
         {
             DrawingBackendApi.Current.CanvasImplementation.Clear(ObjectPointer, color);
+            Changed?.Invoke(null);
         }
 
         public void DrawLine(VecI from, VecI to, Paint paint)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawLine(ObjectPointer, from, to, paint);
+            Changed?.Invoke(new RectD(from, to));
         }
 
         public void Flush()
@@ -146,6 +164,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
         public void DrawColor(Color color, BlendMode paintBlendMode)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawColor(ObjectPointer, color, paintBlendMode);
+            Changed?.Invoke(null);
         }
 
         public void RotateRadians(float dataAngle, float centerX, float centerY)
@@ -156,6 +175,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
         public void DrawBitmap(Bitmap bitmap, int x, int y)
         {
             DrawingBackendApi.Current.CanvasImplementation.DrawBitmap(ObjectPointer, bitmap, x, y);
+            Changed?.Invoke(null);
         }
 
         public override void Dispose()

+ 11 - 1
src/PixiEditor.DrawingApi.Core/Surface/DrawingSurface.cs

@@ -10,12 +10,14 @@ namespace PixiEditor.DrawingApi.Core.Surface
     {
         public override object Native => DrawingBackendApi.Current.SurfaceImplementation.GetNativeSurface(ObjectPointer);
         public Canvas Canvas { get; private set; }
+        public event SurfaceChangedEventHandler? Changed;
 
         public DrawingSurface(IntPtr objPtr, Canvas canvas) : base(objPtr)
         {
             Canvas = canvas;
+            Canvas.Changed += OnCanvasChanged;
         }
-        
+
         public static DrawingSurface Create(Pixmap imageInfo)
         {
             return DrawingBackendApi.Current.SurfaceImplementation.Create(imageInfo);
@@ -24,6 +26,7 @@ namespace PixiEditor.DrawingApi.Core.Surface
         public void Draw(Canvas drawingSurfaceCanvas, int x, int y, Paint drawingPaint)
         {
             DrawingBackendApi.Current.SurfaceImplementation.Draw(this, drawingSurfaceCanvas, x, y, drawingPaint);
+            Changed?.Invoke(null);
         }
 
         public Image Snapshot()
@@ -63,7 +66,14 @@ namespace PixiEditor.DrawingApi.Core.Surface
 
         public override void Dispose()
         {
+            Canvas.Changed -= OnCanvasChanged;
+            Canvas.Dispose(); // TODO: make sure this is correct
             DrawingBackendApi.Current.SurfaceImplementation.Dispose(this);
         }
+
+        private void OnCanvasChanged(RectD? changedrect)
+        {
+            Changed?.Invoke(changedrect);
+        }
     }
 }

+ 1 - 1
src/PixiEditor.Extensions/Windowing/IPopupWindow.cs

@@ -2,7 +2,7 @@
 
 public interface IPopupWindow
 {
-    public string Title { get; set; }
+    public string? Title { get; set; }
     public void Show();
     public void Close();
     public Task<bool?> ShowDialog();