Explorar o código

Merge branch 'brush-engine' of https://github.com/PixiEditor/PixiEditor into brush-engine

flabbet hai 2 semanas
pai
achega
89d1e71c77

+ 1 - 1
README.md

@@ -8,7 +8,7 @@
 
 <img width="50%" align="center" src="https://github.com/user-attachments/assets/bd08c8bd-f610-449d-b1e2-6a990e562518">
 
-## The only 2D Graphics Editor you'll ever need
+## The All-in-One Editor for 2D
 
 **PixiEditor** is a universal 2D editor that was made to provide you with tools and features for all your 2D needs. Create beautiful sprites for your games, animations, edit images, create logos. All packed up in an intuitive and familiar interface.
 

+ 2 - 0
src/ChunkyImageLib/ChunkyImage.cs

@@ -1314,6 +1314,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
         lock (lockObject)
         {
             ThrowIfDisposed();
+            using var ctx = DrawingBackendApi.Current.RenderingDispatcher.EnsureContext();
             var dict = new Dictionary<VecI, Surface>();
             foreach (var (pos, chunk) in committedChunks[ChunkResolution.Full])
             {
@@ -1322,6 +1323,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable, ICloneable, ICache
                     var surf = new Surface(chunk.Surface.ImageInfo);
                     surf.DrawingSurface.Canvas.DrawSurface(chunk.Surface.DrawingSurface, 0, 0);
                     dict[pos] = surf;
+                    surf.DrawingSurface.Flush();
                 }
             }
 

+ 1 - 1
src/ColorPicker

@@ -1 +1 @@
-Subproject commit 8bde664b59049ad9f300f946b3ba433a25495cca
+Subproject commit 1de89b352c0fdeb9487a98b9093828fb295a9df2

+ 1 - 1
src/PixiDocks

@@ -1 +1 @@
-Subproject commit cc31ace86078f0cb89a36fe568e4cbe8e3d8bea0
+Subproject commit 078670ac485e8cb92ec84bd88d112713219e3256

+ 32 - 5
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/PathVectorData.cs

@@ -11,11 +11,37 @@ namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 {
     public VectorPath Path { get; set; }
-    public override RectD GeometryAABB => Path?.TightBounds ?? RectD.Empty;
+
+    public override RectD GeometryAABB
+    {
+        get
+        {
+            var tightBounds = Path?.TightBounds ?? RectD.Empty;
+            if (tightBounds.Width == 0 || tightBounds.Height == 0)
+            {
+                // If the path is a line or a point, we need to inflate the bounds by half the stroke width
+                double halfStroke = StrokeWidth / 2;
+                tightBounds = new RectD(
+                    tightBounds.X - halfStroke,
+                    tightBounds.Y - halfStroke,
+                    tightBounds.Width + StrokeWidth,
+                    tightBounds.Height + StrokeWidth);
+            }
+
+            return tightBounds;
+        }
+    }
+
     public override RectD VisualAABB => GeometryAABB.Inflate(StrokeWidth / 2);
 
-    public override ShapeCorners TransformationCorners =>
-        new ShapeCorners(Path.TightBounds).WithMatrix(TransformationMatrix);
+    public override ShapeCorners TransformationCorners
+    {
+        get
+        {
+            var tightCorners = new ShapeCorners(GeometryAABB);
+            return tightCorners.WithMatrix(TransformationMatrix);
+        }
+    }
 
     public StrokeCap StrokeLineCap { get; set; } = StrokeCap.Round;
 
@@ -133,8 +159,9 @@ public class PathVectorData : ShapeVectorData, IReadOnlyPathData
 
     protected bool Equals(PathVectorData other)
     {
-        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap && StrokeLineJoin == other.StrokeLineJoin
-                && FillType == other.FillType;
+        return base.Equals(other) && Path.Equals(other.Path) && StrokeLineCap == other.StrokeLineCap &&
+               StrokeLineJoin == other.StrokeLineJoin
+               && FillType == other.FillType;
     }
 
     public override bool Equals(object? obj)

+ 29 - 17
src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_Change.cs → src/PixiEditor.ChangeableDocument/Changes/Selection/MagicWand/MagicWand_UpdateableChange.cs

@@ -6,7 +6,7 @@ using Drawie.Numerics;
 
 namespace PixiEditor.ChangeableDocument.Changes.Selection.MagicWand;
 
-internal class MagicWand_Change : Change
+internal class MagicWand_UpdateableChange : UpdateableChange
 {
     private VectorPath? originalPath;
     private VectorPath path = new() { FillType = PathFillType.EvenOdd };
@@ -16,8 +16,8 @@ internal class MagicWand_Change : Change
     private int frame;
     private double tolerance;
 
-    [GenerateMakeChangeAction]
-    public MagicWand_Change(List<Guid> memberGuids, VecI point, SelectionMode mode, double tolerance, int frame)
+    [GenerateUpdateableChangeActions]
+    public MagicWand_UpdateableChange(List<Guid> memberGuids, VecI point, SelectionMode mode, double tolerance, int frame)
     {
         path.MoveTo(point);
         this.mode = mode;
@@ -33,7 +33,24 @@ internal class MagicWand_Change : Change
         return true;
     }
 
+    [UpdateChangeMethod]
+    public void Update(VecI point)
+    {
+        this.point = point;
+    }
+
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        ignoreInUndo = false;
+        return CommonApply(target);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        return CommonApply(target);
+    }
+
+    private Selection_ChangeInfo CommonApply(Document target)
     {
         HashSet<Guid> membersToReference = new();
 
@@ -45,19 +62,6 @@ internal class MagicWand_Change : Change
 
         path = MagicWandHelper.DoMagicWandFloodFill(point, membersToReference, tolerance, target, frame);
 
-        ignoreInUndo = false;
-        return CommonApply(target);
-    }
-
-    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
-    {
-        (var toDispose, target.Selection.SelectionPath) = (target.Selection.SelectionPath, new VectorPath(originalPath!));
-        toDispose.Dispose();
-        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
-    }
-
-    private Selection_ChangeInfo CommonApply(Document target)
-    {
         var toDispose = target.Selection.SelectionPath;
         if (mode == SelectionMode.New)
         {
@@ -67,13 +71,21 @@ internal class MagicWand_Change : Change
         }
         else
         {
-            target.Selection.SelectionPath = originalPath!.Op(path, mode.ToVectorPathOp());
+            target.Selection.SelectionPath = target.Selection.SelectionPath!.Op(path, mode.ToVectorPathOp());
         }
         toDispose.Dispose();
 
         return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
     }
 
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var toDispose = target.Selection.SelectionPath;
+        target.Selection.SelectionPath = new VectorPath(originalPath!);
+        toDispose.Dispose();
+        return new Selection_ChangeInfo(new VectorPath(target.Selection.SelectionPath));
+    }
+
     public override void Dispose()
     {
         path.Dispose();

+ 3 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -312,7 +312,9 @@
   "LINE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move to draw a line. Hold Shift to enable snapping.",
   "LINE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a line with snapping enabled.",
   "MAGIC_WAND_TOOL_TOOLTIP": "Magic Wand ({0}). Flood's the selection",
-  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection.",
+  "MAGIC_WAND_ACTION_DISPLAY": "Click to flood the selection. Hold Shift to add to existing selection. Hold Ctrl to subtract from it.",
+  "MAGIC_WAND_ACTION_DISPLAY_SHIFT": "Click to add to the current selection.",
+  "MAGIC_WAND_ACTION_DISPLAY_CTRL": "Click to subtract from the current selection.",
   "PEN_TOOL": "Pen",
   "BRIGHTNESS_TOOL": "Brightness",
   "COLOR_PICKER_TOOL": "Color Picker",

+ 3 - 0
src/PixiEditor/Helpers/SurfaceHelpers.cs

@@ -2,8 +2,10 @@
 using ChunkyImageLib;
 using PixiEditor.Helpers.Extensions;
 using Drawie.Backend.Core;
+using Drawie.Backend.Core.Bridge;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Surfaces.ImageData;
+using Drawie.Interop.Avalonia.Core;
 using Drawie.Numerics;
 
 namespace PixiEditor.Helpers;
@@ -38,6 +40,7 @@ public static class SurfaceHelpers
 
     public static unsafe byte[] ToByteArray(this Surface surface, ColorType colorType = ColorType.Bgra8888, AlphaType alphaType = AlphaType.Premul, ColorSpace colorSpace = null)
     {
+        using var ctx = IDrawieInteropContext.Current.EnsureContext();
         int width = surface.Size.X;
         int height = surface.Size.Y;
         var imageInfo = new ImageInfo(width, height, colorType, alphaType, colorSpace == null ? surface.ImageInfo.ColorSpace : colorSpace);

+ 20 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/MagicWandToolExecutor.cs

@@ -4,6 +4,7 @@ using Drawie.Numerics;
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.Handlers.Tools;
 using PixiEditor.Models.Tools;
 
@@ -25,7 +26,7 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         if (magicWand is null || members.Count == 0)
             return ExecutionState.Error;
 
-        mode = magicWand.SelectMode;
+        mode = magicWand.ResultingSelectionMode;
         memberGuids = members;
         considerAllLayers = magicWand.DocumentScope == DocumentScope.Canvas;
         if (considerAllLayers)
@@ -33,19 +34,34 @@ internal class MagicWandToolExecutor : UpdateableChangeExecutor
         var pos = controller!.LastPixelPosition;
         tolerance = (float)magicWand.Tolerance;
 
-        internals!.ActionAccumulator.AddActions(new MagicWand_Action(memberGuids, pos, mode, tolerance, document!.AnimationHandler.ActiveFrameBindable));
+        AddUpdateAction(pos);
 
         return ExecutionState.Success;
     }
 
+    public override void OnPixelPositionChange(VecI pos, MouseOnCanvasEventArgs args)
+    {
+        AddUpdateAction(pos);
+    }
+
     public override void OnLeftMouseButtonUp(VecD argsPositionOnCanvas)
     {
-        internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
+        AddFinishAction();
         onEnded!(this);
     }
 
     public override void ForceStop()
     {
-        internals!.ActionAccumulator.AddActions(new ChangeBoundary_Action());
+        AddFinishAction();
+    }
+
+    private void AddUpdateAction(VecI pos)
+    {
+        var action = new MagicWand_Action(memberGuids, pos, mode, tolerance, document!.AnimationHandler.ActiveFrameBindable);
+        internals!.ActionAccumulator.AddActions(action);
+    }
+    private void AddFinishAction()
+    {
+        internals!.ActionAccumulator.AddFinishedActions(new EndMagicWand_Action());
     }
 }

+ 1 - 0
src/PixiEditor/Models/Files/PixiFileType.cs

@@ -38,6 +38,7 @@ internal class PixiFileType : IoFileType
         catch (Exception e)
         {
             CrashHelper.SendExceptionInfo(e);
+            Console.WriteLine("Failed to save document: " + e.Message + Environment.NewLine + e.StackTrace);
             return new SaveResult(SaveResultType.UnknownError);
         }
 

+ 1 - 1
src/PixiEditor/Models/Handlers/Tools/IMagicWandToolHandler.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.Models.Handlers.Tools;
 
 internal interface IMagicWandToolHandler : IToolHandler
 {
-    public SelectionMode SelectMode { get; }
+    public SelectionMode ResultingSelectionMode { get; }
     public DocumentScope DocumentScope { get; }
     public float Tolerance { get; }
 }

+ 23 - 3
src/PixiEditor/ViewModels/Tools/Tools/MagicWandToolViewModel.cs

@@ -18,10 +18,11 @@ namespace PixiEditor.ViewModels.Tools.Tools;
 internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
 {
     public override LocalizedString Tooltip => new LocalizedString("MAGIC_WAND_TOOL_TOOLTIP", Shortcut);
-
+    private LocalizedString defaultActionDisplay => new LocalizedString("MAGIC_WAND_ACTION_DISPLAY");
     public override string ToolNameLocalizationKey => "MAGIC_WAND_TOOL";
     public override Type[]? SupportedLayerTypes { get; } = { typeof(IRasterLayerHandler) };
-
+    private SelectionMode KeyModifierSelectionMode = SelectionMode.New;
+    public SelectionMode ResultingSelectionMode => KeyModifierSelectionMode != SelectionMode.New ? KeyModifierSelectionMode : SelectMode;
     [Settings.Enum("MODE_LABEL")]
     public SelectionMode SelectMode => GetValue<SelectionMode>();
 
@@ -36,7 +37,7 @@ internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
     public MagicWandToolViewModel()
     {
         Toolbar = ToolbarFactory.Create(this);
-        ActionDisplay = "MAGIC_WAND_ACTION_DISPLAY";
+        ActionDisplay = defaultActionDisplay;
     }
 
     public override Type LayerTypeToCreateOnEmptyUse { get; } = null;
@@ -45,4 +46,23 @@ internal class MagicWandToolViewModel : ToolViewModel, IMagicWandToolHandler
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseMagicWandTool();
     }
+
+    public override void KeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown, Key argsKey)
+    {
+        if (shiftIsDown)
+        {
+            ActionDisplay = new LocalizedString("MAGIC_WAND_ACTION_DISPLAY_SHIFT");
+            KeyModifierSelectionMode = SelectionMode.Add;
+        }
+        else if (ctrlIsDown)
+        {
+            ActionDisplay = new LocalizedString("MAGIC_WAND_ACTION_DISPLAY_CTRL");
+            KeyModifierSelectionMode = SelectionMode.Subtract;
+        }
+        else
+        {
+            ActionDisplay = defaultActionDisplay;
+            KeyModifierSelectionMode = SelectionMode.New;
+        }
+    }
 }