Forráskód Böngészése

Reference layer and draw checker background skia operation

Krzysztof Krysiński 1 éve
szülő
commit
cfa47f98cb

+ 5 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/ScaleToBitmapScalingModeConverter.cs

@@ -9,6 +9,11 @@ internal class ScaleToBitmapScalingModeConverter : SingleInstanceConverter<Scale
     {
         if (value is not double scale)
             return BitmapInterpolationMode.None;
+        return Calculate(scale);
+    }
+
+    public static BitmapInterpolationMode Calculate(double scale)
+    {
         if (scale < 1)
             return BitmapInterpolationMode.HighQuality;
         return BitmapInterpolationMode.None;

+ 12 - 0
src/PixiEditor.AvaloniaUI/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -43,7 +43,11 @@ internal class ViewportOverlays
         transformOverlay = new TransformOverlay();
         BindTransformOverlay();
 
+        referenceLayerOverlay = new ReferenceLayerOverlay();
+        BindReferenceLayerOverlay();
+
         Viewport.ActiveOverlays.Add(gridLinesOverlayOverlay);
+        Viewport.ActiveOverlays.Add(referenceLayerOverlay);
         Viewport.ActiveOverlays.Add(selectionOverlay);
         Viewport.ActiveOverlays.Add(symmetryOverlay);
         Viewport.ActiveOverlays.Add(lineToolOverlay);
@@ -52,6 +56,13 @@ internal class ViewportOverlays
 
     private void BindReferenceLayerOverlay()
     {
+        Binding isVisibleBinding = new()
+        {
+            Source = Viewport,
+            Path = "Document.ReferenceLayerViewModel.IsVisibleBindable",
+            Mode = BindingMode.OneWay
+        };
+
         Binding referenceLayerBinding = new()
         {
             Source = Viewport,
@@ -73,6 +84,7 @@ internal class ViewportOverlays
             Mode = BindingMode.OneWay,
         };
 
+        referenceLayerOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
         referenceLayerOverlay.Bind(ReferenceLayerOverlay.ReferenceLayerProperty, referenceLayerBinding);
         referenceLayerOverlay.Bind(ReferenceLayerOverlay.ReferenceShapeProperty, referenceShapeBinding);
         referenceLayerOverlay.Bind(ReferenceLayerOverlay.FadeOutProperty, fadeOutBinding);

+ 8 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/Overlay.cs

@@ -15,6 +15,8 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
 {
     public List<Handle> Handles { get; } = new();
 
+    public virtual OverlayRenderSorting OverlayRenderSorting => OverlayRenderSorting.Foreground;
+
     public static readonly StyledProperty<double> ZoomScaleProperty =
         AvaloniaProperty.Register<Overlay, double>(nameof(ZoomScale), defaultValue: 1.0);
 
@@ -144,3 +146,9 @@ public abstract class Overlay : Decorator, IOverlay // TODO: Maybe make it not a
         }
     }
 }
+
+public enum OverlayRenderSorting
+{
+    Background,
+    Foreground
+}

+ 15 - 8
src/PixiEditor.AvaloniaUI/Views/Overlays/ReferenceLayerOverlay.cs

@@ -5,13 +5,13 @@ using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
 using Avalonia.Media;
 using ChunkyImageLib.DataHolders;
+using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.Views.Visuals;
 using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays;
 
-[PseudoClasses(":showHighest", ":fadedOut")]
 internal class ReferenceLayerOverlay : Overlay
 {
     public static readonly StyledProperty<ReferenceLayerViewModel> ReferenceLayerProperty = AvaloniaProperty.Register<ReferenceLayerOverlay, ReferenceLayerViewModel>(
@@ -29,21 +29,24 @@ internal class ReferenceLayerOverlay : Overlay
         set => SetValue(ReferenceShapeProperty, value);
     }
 
+    public ReferenceLayerViewModel ReferenceLayer
+    {
+        get => GetValue(ReferenceLayerProperty);
+        set => SetValue(ReferenceLayerProperty, value);
+    }
+
+
     public bool FadeOut
     {
         get => GetValue(FadeOutProperty);
         set => SetValue(FadeOutProperty, value);
     }
 
-    public double ReferenceLayerScale => ZoomScale * ((ReferenceLayer.ReferenceBitmap != null && ReferenceShape != null)
+    public double ReferenceLayerScale => ((ReferenceLayer.ReferenceBitmap != null && ReferenceShape != null)
         ? (ReferenceShape.RectSize.X / (double)ReferenceLayer.ReferenceBitmap.Size.X)
         : 1);
 
-    public ReferenceLayerViewModel ReferenceLayer
-    {
-        get => GetValue(ReferenceLayerProperty);
-        set => SetValue(ReferenceLayerProperty, value);
-    }
+    public override OverlayRenderSorting OverlayRenderSorting => ReferenceLayer.IsTopMost ? OverlayRenderSorting.Foreground : OverlayRenderSorting.Background;
 
     static ReferenceLayerOverlay()
     {
@@ -53,10 +56,14 @@ internal class ReferenceLayerOverlay : Overlay
 
     public override void RenderOverlay(DrawingContext context, RectD dirtyCanvasBounds)
     {
+        //TODO: opacity + animation + border
         if (ReferenceLayer is { ReferenceBitmap: not null })
         {
+            using var renderOptions = context.PushRenderOptions(new RenderOptions { BitmapInterpolationMode = ScaleToBitmapScalingModeConverter.Calculate(ReferenceLayerScale) });
+            using var matrix = context.PushTransform(ReferenceLayer.ReferenceTransformMatrix);
+
             Rect dirtyRect = new Rect(dirtyCanvasBounds.X, dirtyCanvasBounds.Y, dirtyCanvasBounds.Width, dirtyCanvasBounds.Height);
-            DrawSurfaceOperation drawOperation = new DrawSurfaceOperation(dirtyRect, ReferenceLayer.ReferenceBitmap, Stretch.Uniform, Opacity);
+            DrawSurfaceOperation drawOperation = new DrawSurfaceOperation(dirtyRect, ReferenceLayer.ReferenceBitmap, Stretch.None, Opacity);
             context.Custom(drawOperation);
         }
     }

+ 48 - 0
src/PixiEditor.AvaloniaUI/Views/Rendering/DrawCheckerBackgroundOperation.cs

@@ -0,0 +1,48 @@
+using Avalonia;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+using PixiEditor.AvaloniaUI.Helpers.Converters;
+using PixiEditor.AvaloniaUI.Views.Visuals;
+
+namespace PixiEditor.AvaloniaUI.Views.Rendering;
+
+internal class DrawCheckerBackgroundOperation : SkiaDrawOperation
+{
+    public SKBitmap CheckerBitmap { get; set; }
+    public SKRect SurfaceRectToRender { get; set; }
+
+    private SKPaint _checkerPaint;
+
+    public DrawCheckerBackgroundOperation(Rect dirtyBounds, SKBitmap checkerBitmap, float scale,
+        SKRect surfaceRectToRender) : base(dirtyBounds)
+    {
+        SurfaceRectToRender = surfaceRectToRender;
+        CheckerBitmap = checkerBitmap;
+
+        float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, scale) * 0.25f;
+        _checkerPaint = new SKPaint()
+        {
+            Shader = SKShader.CreateBitmap(
+                CheckerBitmap,
+                SKShaderTileMode.Repeat, SKShaderTileMode.Repeat,
+                SKMatrix.CreateScale(checkerScale, checkerScale)),
+            FilterQuality = SKFilterQuality.None,
+        };
+    }
+
+    public override void Render(ISkiaSharpApiLease lease)
+    {
+        var canvas = lease.SkCanvas;
+        canvas.DrawRect(SurfaceRectToRender, _checkerPaint);
+    }
+
+    public override bool Equals(ICustomDrawOperation? other)
+    {
+        if (other is DrawCheckerBackgroundOperation operation)
+        {
+            return operation.CheckerBitmap == CheckerBitmap && operation.SurfaceRectToRender == SurfaceRectToRender;
+        }
+
+        return false;
+    }
+}

+ 36 - 43
src/PixiEditor.AvaloniaUI/Views/Rendering/Scene.cs

@@ -19,8 +19,9 @@ using PixiEditor.AvaloniaUI.Views.Overlays;
 using PixiEditor.AvaloniaUI.Views.Overlays.Pointers;
 using PixiEditor.AvaloniaUI.Views.Visuals;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.DrawingApi.Skia;
 using PixiEditor.Extensions.UI.Overlays;
-using Bitmap = Avalonia.Media.Imaging.Bitmap;
+using Bitmap = PixiEditor.DrawingApi.Core.Surface.Bitmap;
 using Image = PixiEditor.DrawingApi.Core.Surface.ImageData.Image;
 using Point = Avalonia.Point;
 
@@ -77,8 +78,6 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
 
     private Bitmap? checkerBitmap;
 
-    private Brush? checkerBrush;
-
     private Overlay? capturedOverlay;
 
     private List<Overlay> mouseOverOverlays = new();
@@ -118,14 +117,11 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             angle = 360 - angle;
         }
 
-        VecD dirtyDimensions = new VecD(ContentDimensions.X * Scale, ContentDimensions.Y * Scale);
-        VecD dirtyCenterShift = new VecD(FlipX ? -dirtyDimensions.X / 2 : dirtyDimensions.X / 2, FlipY ? -dirtyDimensions.Y / 2 : dirtyDimensions.Y / 2);
-        VecD dirtyCenter = new VecD(CanvasPos.X + dirtyCenterShift.X, CanvasPos.Y + dirtyCenterShift.Y);
-        RectD dirtyBounds = new ShapeCorners(dirtyCenter, dirtyDimensions)
-            .AsRotated(MathUtil.DegreesToRadians(angle), new VecD(CanvasPos.X, CanvasPos.Y)).AABBBounds;
+        RectD dirtyBounds = new RectD(0, 0, Document.Width, Document.Height);
+        Rect dirtyRect = new Rect(0, 0, Document.Width, Document.Height);
 
         using var operation = new DrawSceneOperation(Surface, Document, CanvasPos, Scale, angle, FlipX, FlipY,
-            new Rect(dirtyBounds.X, dirtyBounds.Y, dirtyBounds.Width, dirtyBounds.Height),
+            dirtyRect,
             Bounds,
             Opacity);
 
@@ -133,16 +129,29 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         context.PushTransform(matrix);
         context.PushRenderOptions(new RenderOptions { BitmapInterpolationMode = BitmapInterpolationMode.None });
 
-        DrawCheckerboard(context);
+        float resolutionScale = CalculateResolutionScale();
+        var resolutionTransformation = context.PushTransform(Matrix.CreateScale(resolutionScale, resolutionScale));
+
+        DrawCheckerboard(context, dirtyRect, operation.SurfaceRectToRender);
 
+        resolutionTransformation.Dispose();
+        DrawOverlays(context, dirtyBounds, OverlayRenderSorting.Background);
+
+        resolutionTransformation = context.PushTransform(Matrix.CreateScale(resolutionScale, resolutionScale));
         context.Custom(operation);
 
+        resolutionTransformation.Dispose();
+        DrawOverlays(context, dirtyBounds, OverlayRenderSorting.Foreground);
+    }
+
+    private void DrawOverlays(DrawingContext context, RectD dirtyBounds, OverlayRenderSorting sorting)
+    {
         if (ActiveOverlays != null)
         {
             foreach (Overlay overlay in ActiveOverlays)
             {
+                if (!overlay.IsVisible || overlay.OverlayRenderSorting != sorting) continue;
                 overlay.ZoomScale = Scale;
-                if (!overlay.IsVisible) continue;
 
                 overlay.RenderOverlay(context, dirtyBounds);
                 Cursor = overlay.Cursor;
@@ -150,6 +159,16 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         }
     }
 
+    private void DrawCheckerboard(DrawingContext context, Rect dirtyBounds, RectI operationSurfaceRectToRender)
+    {
+        DrawCheckerBackgroundOperation checkerOperation = new DrawCheckerBackgroundOperation(
+            dirtyBounds,
+            (SKBitmap)checkerBitmap.Native,
+            (float)Scale,
+            operationSurfaceRectToRender.ToSkRect());
+        context.Custom(checkerOperation);
+    }
+
     protected override void OnPointerEntered(PointerEventArgs e)
     {
         base.OnPointerEntered(e);
@@ -309,22 +328,12 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
         return transform;
     }
 
-    private void DrawCheckerboard(DrawingContext context)
+    private float CalculateResolutionScale()
     {
-        if (checkerBitmap != null)
-        {
-            float checkerScale = (float)ZoomToViewportConverter.ZoomToViewport(16, Scale);
-            checkerBrush = new ImageBrush
-            {
-                Source = checkerBitmap,
-                TileMode = TileMode.Tile,
-                DestinationRect = new RelativeRect(0, 0, checkerScale, checkerScale, RelativeUnit.Absolute),
-                Transform = new ScaleTransform(0.5f, 0.5f)
-            };
-
-            Rect surfaceRect = new(0, 0, Document.Width, Document.Height);
-            context.DrawRectangle(checkerBrush, null, surfaceRect);
-        }
+        float scaleX = (float)Document.Width / Surface.Size.X;
+        float scaleY = (float)Document.Height / Surface.Size.Y;
+        var scaleUniform = Math.Min(scaleX, scaleY);
+        return scaleUniform;
     }
 
     private void CaptureOverlay(Overlay? overlay, IPointer pointer)
@@ -392,17 +401,11 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
     {
         if (e.NewValue is string path)
         {
-            scene.checkerBitmap = ImagePathToBitmapConverter.LoadBitmapFromRelativePath(path);
-            scene.checkerBrush = new ImageBrush
-            {
-                Source = scene.checkerBitmap,
-                TileMode = TileMode.Tile
-            };
+            scene.checkerBitmap = ImagePathToBitmapConverter.LoadDrawingApiBitmapFromRelativePath(path);
         }
         else
         {
             scene.checkerBitmap = null;
-            scene.checkerBrush = null;
         }
     }
 
@@ -458,8 +461,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
 
         canvas.Save();
 
-        canvas.Scale(CalculateResolutionScale());
-
         if (SurfaceRectToRender.IsZeroOrNegativeArea)
         {
             canvas.Restore();
@@ -472,14 +473,6 @@ internal class DrawSceneOperation : SkiaDrawOperation
         canvas.Restore();
     }
 
-    private float CalculateResolutionScale()
-    {
-        float scaleX = (float)Document.Width / Surface.Size.X;
-        float scaleY = (float)Document.Height / Surface.Size.Y;
-        var scaleUniform = Math.Min(scaleX, scaleY);
-        return scaleUniform;
-    }
-
     private RectI FindRectToRender(float finalScale)
     {
         ShapeCorners surfaceInViewportSpace = SurfaceToViewport(new RectI(VecI.Zero, Surface.Size), finalScale);