Ver código fonte

Merge branch 'master' into autosave

# Conflicts:
#	src/PixiEditor/Models/IO/Exporter.cs
CPKreuz 1 ano atrás
pai
commit
cd4c9a7595
26 arquivos alterados com 108 adições e 58 exclusões
  1. 1 1
      src/ChunkyImageLib/DataHolders/AffectedArea.cs
  2. 3 3
      src/ChunkyImageLib/DataHolders/ColorBounds.cs
  3. 1 1
      src/ChunkyImageLib/Operations/OperationHelper.cs
  4. 2 0
      src/ChunkyImageLib/Surface.cs
  5. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs
  6. 6 2
      src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs
  7. 1 7
      src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs
  8. 3 3
      src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs
  9. 1 1
      src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs
  10. 6 5
      src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs
  11. 2 2
      src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs
  12. 1 1
      src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs
  13. 2 2
      src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs
  14. 6 2
      src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs
  15. 1 1
      src/PixiEditor.Zoombox/Operations/ZoomDragOperation.cs
  16. 1 1
      src/PixiEditor.Zoombox/Zoombox.xaml.cs
  17. 7 2
      src/PixiEditor/Helpers/Converters/ReciprocalConverter.cs
  18. 5 1
      src/PixiEditor/Helpers/ProcessHelper.cs
  19. 5 4
      src/PixiEditor/Models/Controllers/MouseUpdateController.cs
  20. 3 1
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  21. 0 1
      src/PixiEditor/Models/IO/Exporter.cs
  22. 14 3
      src/PixiEditor/Models/IO/Importer.cs
  23. 1 1
      src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs
  24. 17 10
      src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  25. 5 0
      src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  26. 13 2
      src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

+ 1 - 1
src/ChunkyImageLib/DataHolders/AffectedArea.cs

@@ -19,7 +19,7 @@ public struct AffectedArea
     public HashSet<VecI> Chunks { get; set; }
 
     /// <summary>
-    /// A rectangle in global full-scale coordinat
+    /// A rectangle in global full-scale coordinates
     /// </summary>
     public RectI? GlobalArea { get; set; }
 

+ 3 - 3
src/ChunkyImageLib/DataHolders/ColorBounds.cs

@@ -65,10 +65,10 @@ public struct ColorBounds
 
     public bool IsWithinBounds(Color toCompare)
     {
-        float r = toCompare.R / 255f;
-        float g = toCompare.G / 255f;
-        float b = toCompare.B / 255f;
         float a = toCompare.A / 255f;
+        float r = (toCompare.R / 255f) * a;
+        float g = (toCompare.G / 255f) * a;
+        float b = (toCompare.B / 255f) * a;
         
         if (r < LowerR || r > UpperR)
             return false;

+ 1 - 1
src/ChunkyImageLib/Operations/OperationHelper.cs

@@ -234,7 +234,7 @@ public static class OperationHelper
 
     public static HashSet<VecI> FindChunksTouchingRectangle(RectI rect, int chunkSize)
     {
-        if (rect.Width > chunkSize * 40 * 20 || rect.Height > chunkSize * 40 * 20)
+        if (rect.Width > chunkSize * 40 * 20 || rect.Height > chunkSize * 40 * 20 || rect.IsZeroOrNegativeArea)
             return new HashSet<VecI>();
 
         VecI min = GetChunkPos(rect.TopLeft, chunkSize);

+ 2 - 0
src/ChunkyImageLib/Surface.cs

@@ -64,6 +64,8 @@ public class Surface : IDisposable
     public static Surface Load(byte[] encoded)
     {
         using var image = Image.FromEncodedData(encoded);
+        if (image is null)
+            throw new ArgumentException($"The passed byte array does not contain a valid image");
 
         var surface = new Surface(new VecI(image.Width, image.Height));
         surface.DrawingSurface.Canvas.DrawImage(image, 0, 0);

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Drawing/FloodFill/FloodFillHelper.cs

@@ -53,7 +53,7 @@ public static class FloodFillHelper
             static (EmptyChunk _) => Colors.Transparent
         );
 
-        if ((colorToReplace.A == 0 && drawingColor.A == 0) || colorToReplace == drawingColor)
+        if ((drawingColor.A == 0) || colorToReplace == drawingColor)
             return new();
 
         RectI globalSelectionBounds = (RectI?)selection?.TightBounds ?? new RectI(VecI.Zero, document.Size);

+ 6 - 2
src/PixiEditor.ChangeableDocument/Changes/Drawing/TransformSelectedArea_UpdateableChange.cs

@@ -177,9 +177,13 @@ internal class TransformSelectedArea_UpdateableChange : UpdateableChange
     {
         if (hasEnqueudImages)
             throw new InvalidOperationException("Attempted to dispose the change while it's internally stored image is still used enqueued in some ChunkyImage. Most likely someone tried to dispose a change after ApplyTemporarily was called but before the subsequent call to Apply. Don't do that.");
-        foreach (var (_, (image, _)) in images!)
+
+        if (images is not null)
         {
-            image.Dispose();
+            foreach (var (_, (image, _)) in images)
+            {
+                image.Dispose();
+            }
         }
 
         if (savedChunks is not null)

+ 1 - 7
src/PixiEditor.ChangeableDocument/Changes/Root/ClipCanvas_Change.cs

@@ -24,7 +24,7 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
             }
         });
 
-        if (!bounds.HasValue)
+        if (!bounds.HasValue || bounds.Value.IsZeroOrNegativeArea || bounds.Value == new RectI(VecI.Zero, target.Size))
         {
             ignoreInUndo = true;
             return new None();
@@ -48,12 +48,6 @@ internal class ClipCanvas_Change : ResizeBasedChangeBase
             
             Resize(member.Mask, member.GuidValue, newBounds.Size, -newBounds.Pos, deletedMaskChunks);
         });
-        
-        if (newBounds.IsZeroOrNegativeArea)
-        {
-            ignoreInUndo = true;
-            return new None();
-        }
 
         ignoreInUndo = false;
         return new Size_ChangeInfo(newBounds.Size, target.VerticalSymmetryAxisX, target.HorizontalSymmetryAxisY);

+ 3 - 3
src/PixiEditor.ChangeableDocument/Changes/Root/ResizeBasedChangeBase.cs

@@ -19,6 +19,9 @@ internal abstract class ResizeBasedChangeBase : Change
         return true;
     }
     
+    /// <summary>
+    /// Notice: this commits image changes, you won't have a chance to revert or set ignoreInUndo to true
+    /// </summary>
     protected virtual void Resize(ChunkyImage img, Guid memberGuid, VecI size, VecI offset, Dictionary<Guid, CommittedChunkStorage> deletedChunksDict)
     {
         img.EnqueueResize(size);
@@ -31,9 +34,6 @@ internal abstract class ResizeBasedChangeBase : Change
     
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
     {
-        if (target.Size == _originalSize)
-            return new None();
-
         target.Size = _originalSize;
         target.ForEveryMember((member) =>
         {

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Structure/DuplicateLayer_Change.cs

@@ -15,6 +15,7 @@ internal class DuplicateLayer_Change : Change
     {
         if (!target.TryFindMember<Layer>(layerGuid, out Layer? layer))
             return false;
+        duplicateGuid = Guid.NewGuid();
         return true;
     }
 
@@ -23,7 +24,6 @@ internal class DuplicateLayer_Change : Change
         (Layer existingLayer, Folder parent) = ((Layer, Folder))target.FindChildAndParentOrThrow(layerGuid);
 
         Layer clone = existingLayer.Clone();
-        duplicateGuid = Guid.NewGuid();
         clone.GuidValue = duplicateGuid;
 
         int index = parent.Children.IndexOf(existingLayer);

+ 6 - 5
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -247,22 +247,23 @@ public static class ChunkRenderer
         OneOf<All, HashSet<Guid>> membersToMerge,
         RectI? transformedClippingRect)
     {
-        if (folder.Children.Count == 0)
+        var folderChildren = folder.Children;
+        if (folderChildren.Count == 0)
             return new EmptyChunk();
 
         Chunk targetChunk = Chunk.Create(resolution);
         targetChunk.Surface.DrawingSurface.Canvas.Clear();
 
         OneOf<FilledChunk, EmptyChunk, Chunk> clippingChunk = new FilledChunk();
-        for (int i = 0; i < folder.Children.Count; i++)
+        for (int i = 0; i < folderChildren.Count; i++)
         {
-            var child = folder.Children[i];
+            var child = folderChildren[i];
 
             // next child might use clip to member below in which case we need to save the clip image
             bool needToSaveClippingChunk =
-                i < folder.Children.Count - 1 &&
+                i < folderChildren.Count - 1 &&
                 !child.ClipToMemberBelow &&
-                folder.Children[i + 1].ClipToMemberBelow;
+                folderChildren[i + 1].ClipToMemberBelow;
 
             // if the current member doesn't need a clip, get rid of it
             if (!child.ClipToMemberBelow && !clippingChunk.IsT0)

+ 2 - 2
src/PixiEditor.DrawingApi.Core/Bridge/Operations/IImageImplementation.cs

@@ -8,8 +8,8 @@ namespace PixiEditor.DrawingApi.Core.Bridge.Operations
     {
         public Image Snapshot(DrawingSurface drawingSurface);
         public void DisposeImage(Image image);
-        public Image FromEncodedData(string path);
-        public Image FromEncodedData(byte[] dataBytes);
+        public Image? FromEncodedData(string path);
+        public Image? FromEncodedData(byte[] dataBytes);
         public void GetColorShifts(ref int platformColorAlphaShift, ref int platformColorRedShift, ref int platformColorGreenShift, ref int platformColorBlueShift);
         public ImgData Encode(Image image);
         public int GetWidth(IntPtr objectPointer);

+ 1 - 1
src/PixiEditor.DrawingApi.Core/Numerics/RectI.cs

@@ -80,7 +80,7 @@ public struct RectI : IEquatable<RectI>
     public int Width { readonly get => right - left; set => right = left + value; }
     public int Height { readonly get => bottom - top; set => bottom = top + value; }
     public readonly bool IsZeroArea => left == right || top == bottom;
-    public readonly bool IsZeroOrNegativeArea => left >= right || top >= bottom;
+    public readonly bool IsZeroOrNegativeArea => left >= right || top >= bottom || Width < 0 || Height < 0; // checking Width and Height too as they can overflow in large rectangles 
 
     public RectI()
     {

+ 2 - 2
src/PixiEditor.DrawingApi.Core/Surface/ImageData/Image.cs

@@ -26,12 +26,12 @@ namespace PixiEditor.DrawingApi.Core.Surface.ImageData
             DrawingBackendApi.Current.ImageImplementation.DisposeImage(this);
         }
 
-        public static Image FromEncodedData(string path)
+        public static Image? FromEncodedData(string path)
         {
             return DrawingBackendApi.Current.ImageImplementation.FromEncodedData(path);
         }
         
-        public static Image FromEncodedData(byte[] dataBytes)
+        public static Image? FromEncodedData(byte[] dataBytes)
         {
             return DrawingBackendApi.Current.ImageImplementation.FromEncodedData(dataBytes);
         }

+ 6 - 2
src/PixiEditor.DrawingApi.Skia/Implementations/SkiaImageImplementation.cs

@@ -31,9 +31,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             return new Image(snapshot.Handle);
         }
         
-        public Image FromEncodedData(byte[] dataBytes)
+        public Image? FromEncodedData(byte[] dataBytes)
         {
             SKImage img = SKImage.FromEncodedData(dataBytes);
+            if (img is null)
+                return null;
             ManagedInstances[img.Handle] = img;
             
             return new Image(img.Handle);
@@ -45,9 +47,11 @@ namespace PixiEditor.DrawingApi.Skia.Implementations
             ManagedInstances.TryRemove(image.ObjectPointer, out _);
         }
 
-        public Image FromEncodedData(string path)
+        public Image? FromEncodedData(string path)
         {
             var nativeImg = SKImage.FromEncodedData(path);
+            if (nativeImg is null)
+                return null;
             ManagedInstances[nativeImg.Handle] = nativeImg;
             return new Image(nativeImg.Handle);
         }

+ 1 - 1
src/PixiEditor.Zoombox/Operations/ZoomDragOperation.cs

@@ -33,7 +33,7 @@ internal class ZoomDragOperation : IDragOperation
         double deltaX = curScreenPos.X - screenScaleOrigin.X;
         double deltaPower = deltaX / 10.0;
 
-        parent.Scale = originalScale * Math.Pow(Zoombox.ScaleFactor, deltaPower);
+        parent.Scale = Math.Clamp(originalScale * Math.Pow(Zoombox.ScaleFactor, deltaPower), parent.MinScale, Zoombox.MaxScale);
 
         VecD shiftedOrigin = parent.ToZoomboxSpace(screenScaleOrigin);
         parent.Center += scaleOrigin - shiftedOrigin;

+ 1 - 1
src/PixiEditor.Zoombox/Zoombox.xaml.cs

@@ -284,7 +284,7 @@ public partial class Zoombox : ContentControl, INotifyPropertyChanged
         Angle = 0;
         FlipX = false;
         FlipY = false;
-        Scale = 1 / scaleFactor;
+        Scale = Math.Clamp(1 / scaleFactor, MinScale, MaxScale);
         Center = newSize / 2;
     }
 

+ 7 - 2
src/PixiEditor/Helpers/Converters/ReciprocalConverter.cs

@@ -8,8 +8,13 @@ internal class ReciprocalConverter : SingleInstanceConverter<ReciprocalConverter
     {
         if (value is not double num)
             return DependencyProperty.UnsetValue;
+
+        double result;
         if (parameter is not double mult)
-            return 1 / num;
-        return mult / num;
+            result = 1 / num;
+        else
+            result = mult / num;
+
+        return Math.Clamp(result, 1e-15, 1e15);
     }
 }

+ 5 - 1
src/PixiEditor/Helpers/ProcessHelper.cs

@@ -3,6 +3,7 @@ using System.Diagnostics;
 using System.IO;
 using System.Security.Principal;
 using System.Windows.Input;
+using PixiEditor.Exceptions;
 
 namespace PixiEditor.Helpers;
 
@@ -32,10 +33,13 @@ internal static class ProcessHelper
         {
             string fixedPath = Path.GetFullPath(path);
             var process = Process.Start("explorer.exe", $"/select,\"{fixedPath}\"");
-
             // Explorer might need a second to show up
             process.WaitForExit(500);
         }
+        catch (Win32Exception)
+        {
+            throw new RecoverableException("ERROR_FAILED_TO_OPEN_EXPLORER");
+        }
         finally
         {
             Mouse.OverrideCursor = null;

+ 5 - 4
src/PixiEditor/Models/Controllers/MouseUpdateController.cs

@@ -21,7 +21,6 @@ public class MouseUpdateController : IDisposable
         element = uiElement;
         
         _timer = new System.Timers.Timer(MouseUpdateIntervalMs);
-        _timer.AutoReset = true;
         _timer.Elapsed += TimerOnElapsed;
         
         element.MouseMove += OnMouseMove;
@@ -29,8 +28,10 @@ public class MouseUpdateController : IDisposable
 
     private void TimerOnElapsed(object sender, ElapsedEventArgs e)
     {
-        _timer.Stop();
-        element.MouseMove += OnMouseMove;
+        Application.Current?.Dispatcher.Invoke(() =>
+        {
+            element.MouseMove += OnMouseMove;
+        });
     }
 
     private void OnMouseMove(object sender, MouseEventArgs e)
@@ -42,7 +43,7 @@ public class MouseUpdateController : IDisposable
 
     public void Dispose()
     {
-        _timer.Dispose();
         element.MouseMove -= OnMouseMove;
+        _timer.Dispose();
     }
 }

+ 3 - 1
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -190,12 +190,14 @@ internal class DocumentUpdater
 
     private void ProcessSetSelectedMember(SetSelectedMember_PassthroughAction info)
     {
+        StructureMemberViewModel? member = doc.StructureHelper.Find(info.GuidValue);
+        if (member is null || member.Selection == StructureMemberSelectionType.Hard)
+            return;
         if (doc.SelectedStructureMember is { } oldMember)
         {
             oldMember.Selection = StructureMemberSelectionType.None;
             oldMember.RaisePropertyChanged(nameof(oldMember.Selection));
         }
-        StructureMemberViewModel? member = doc.StructureHelper.FindOrThrow(info.GuidValue);
         member.Selection = StructureMemberSelectionType.Hard;
         member.RaisePropertyChanged(nameof(member.Selection));
         doc.InternalSetSelectedMember(member);

+ 0 - 1
src/PixiEditor/Models/IO/Exporter.cs

@@ -201,7 +201,6 @@ internal class Exporter
         {
             return SaveResult.UnknownError;
         }
-
         return SaveResult.Success;
     }
 }

+ 14 - 3
src/PixiEditor/Models/IO/Importer.cs

@@ -30,8 +30,19 @@ internal class Importer : NotifyableObject
     /// <returns>WriteableBitmap of imported image.</returns>
     public static Surface? ImportImage(string path, VecI size)
     {
-        if (!Path.Exists(path)) return null;
-        Surface original = Surface.Load(path);
+        if (!Path.Exists(path))
+            throw new MissingFileException();
+            
+        Surface original;
+        try
+        {
+            original = Surface.Load(path);
+        }
+        catch (Exception e) when (e is ArgumentException or FileNotFoundException)
+        {
+            throw new CorruptedFileException(e);
+        }
+            
         if (original.Size == size || size == VecI.NegativeOne)
         {
             return original;
@@ -95,7 +106,7 @@ internal class Importer : NotifyableObject
 
                 return doc;
             }
-            catch (InvalidFileException e)
+            catch (Exception e)
             {
                 throw new CorruptedFileException("FAILED_TO_OPEN_FILE", e);
             }

+ 1 - 1
src/PixiEditor/Models/Rendering/MemberPreviewUpdater.cs

@@ -563,7 +563,7 @@ internal class MemberPreviewUpdater
                 continue;
 
             if (tightBounds is null)
-                tightBounds = lastMainPreviewTightBounds[guid];
+                tightBounds = lastMaskPreviewTightBounds[guid];
 
             var previewSize = StructureMemberViewModel.CalculatePreviewSize(tightBounds.Value.Size);
             float scaling = (float)previewSize.X / tightBounds.Value.Width;

+ 17 - 10
src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -385,18 +385,25 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     [Command.Basic("PixiEditor.File.Export", "EXPORT", "EXPORT_IMAGE", CanExecute = "PixiEditor.HasDocument", Key = Key.E, Modifiers = ModifierKeys.Control)]
     public void ExportFile()
     {
-        DocumentViewModel doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
-        if (doc is null)
-            return;
+        try
+        {
+            DocumentViewModel doc = Owner.DocumentManagerSubViewModel.ActiveDocument;
+            if (doc is null)
+                return;
 
-        ExportFileDialog info = new ExportFileDialog(doc.SizeBindable);
-        if (info.ShowDialog())
+            ExportFileDialog info = new ExportFileDialog(doc.SizeBindable);
+            if (info.ShowDialog())
+            {
+                SaveResult result = Exporter.TrySaveUsingDataFromDialog(doc, info.FilePath, info.ChosenFormat, out string finalPath, new(info.FileWidth, info.FileHeight));
+                if (result == SaveResult.Success)
+                    ProcessHelper.OpenInExplorer(finalPath);
+                else
+                    ShowSaveError((DialogSaveResult)result);
+            }
+        }
+        catch (RecoverableException e)
         {
-            SaveResult result = Exporter.TrySaveUsingDataFromDialog(doc, info.FilePath, info.ChosenFormat, out string finalPath, new(info.FileWidth, info.FileHeight));
-            if (result == SaveResult.Success)
-                ProcessHelper.OpenInExplorer(finalPath);
-            else
-                ShowSaveError((DialogSaveResult)result);
+            NoticeDialog.Show(e.DisplayMessage, "ERROR");
         }
     }
 

+ 5 - 0
src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -220,6 +220,11 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
             {
                 NoticeDialog.Show("COULD_NOT_CHECK_FOR_UPDATES", "UPDATE_CHECK_FAILED");
             }
+            catch (Exception e)
+            {
+                CrashHelper.SendExceptionInfoToWebhook(e);
+                NoticeDialog.Show("COULD_NOT_CHECK_FOR_UPDATES", "UPDATE_CHECK_FAILED");
+            }
 
             AskToInstall();
         }

+ 13 - 2
src/PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -3,10 +3,12 @@ using System.Diagnostics;
 using System.IO;
 using System.Windows;
 using System.Windows.Input;
+using PixiEditor.Exceptions;
 using PixiEditor.Extensions.Common.UserPreferences;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Services.NewsFeed;
 using PixiEditor.ViewModels.SubViewModels.Main;
 
@@ -193,8 +195,17 @@ internal partial class HelloTherePopup : Window
 
     private void OpenInExplorer(object parameter)
     {
-        if (parameter is not string value) return;
-        ProcessHelper.OpenInExplorer(value);
+        if (parameter is not string value) 
+            return;
+
+        try
+        {
+            ProcessHelper.OpenInExplorer(value);
+        }
+        catch (RecoverableException e)
+        {
+            NoticeDialog.Show(e.DisplayMessage, "INTERNAL_ERROR");
+        }
     }
 
     private bool CanOpenInExplorer(object parameter) => File.Exists((string)parameter);