Procházet zdrojové kódy

Merge remote-tracking branch 'origin/quickfill' into quickfill

flabbet před 3 roky
rodič
revize
f2fcb7b7f7

+ 49 - 24
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -24,9 +24,8 @@ namespace PixiEditor.Models.Controllers
         private Document activeDocument;
         private Tool selectedTool;
         private Coordinates? startPosition = null;
-        private IEnumerable<Coordinates> cachedHighlight;
         private int halfSize;
-        private BitmapPixelChanges cachedPixels;
+        private SKPaint _highlightPaint;
 
         public BitmapManager()
         {
@@ -40,6 +39,11 @@ namespace PixiEditor.Models.Controllers
             BitmapOperations = new BitmapOperationsUtility(this);
             ReadonlyToolUtility = new ReadonlyToolUtility();
             DocumentChanged += BitmapManager_DocumentChanged;
+            _highlightPaint = new SKPaint
+            {
+                Color = new SKColor(0, 0, 0, 77),
+                Style = SKPaintStyle.StrokeAndFill
+            };
         }
 
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
@@ -226,6 +230,8 @@ namespace PixiEditor.Models.Controllers
                 BitmapOperations.ApplyPreviewLayer();
             }
 
+            HighlightPixels(MousePositionConverter.CurrentCoordinates);
+
             startPosition = null;
         }
 
@@ -236,38 +242,57 @@ namespace PixiEditor.Models.Controllers
                 return;
             }
 
-            if (ToolSize != previewLayerSize || ActiveDocument.PreviewLayer.IsReset)
-            {
-
-                cachedHighlight = CoordinatesCalculator.RectangleToCoordinates(0, 0, ToolSize - 1, ToolSize - 1);
+            var previewLayer = ActiveDocument.PreviewLayer;
 
+            if (ToolSize != previewLayerSize || previewLayer.IsReset)
+            {
                 previewLayerSize = ToolSize;
                 halfSize = (int)Math.Floor(ToolSize / 2f);
+                previewLayer.CreateNewBitmap(ToolSize, ToolSize);
 
-                cachedPixels = BitmapPixelChanges.FromSingleColoredArray(cachedHighlight, new SKColor(0, 0, 0, 77));
+                if (ToolSize != 3)
+                {
+                    float sizeMod = ToolSize % 2 == 0 ? 0 : 0.5f;
+                    if (ToolSize == 1)
+                    {
+                        sizeMod = 1;
+                    }
+
+                    AdjustOffset(newPosition, previewLayer);
+                    int centerX = newPosition.X - previewLayer.OffsetX;
+                    int centerY = newPosition.Y - previewLayer.OffsetY;
+
+                    previewLayer.LayerBitmap.SkiaSurface.Canvas
+                    .DrawOval(
+                        centerX,
+                        centerY,
+                        halfSize + sizeMod,
+                        halfSize + sizeMod,
+                        _highlightPaint);
+                }
+                else
+                {
+                    previewLayer.LayerBitmap.SkiaSurface.Canvas.Clear(_highlightPaint.Color);
+                }
 
-                if (!ActiveDocument.PreviewLayer.IsReset)
-                    ActiveDocument.PreviewLayer.Reset();
-                ActiveDocument.PreviewLayer.SetPixels(cachedPixels);
+                previewLayer.InvokeLayerBitmapChange();
             }
 
-            Coordinates start = newPosition - halfSize;
-            ActiveDocument.PreviewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
+            AdjustOffset(newPosition, previewLayer);
 
-            if (!IsInsideBounds(cachedHighlight))
+            if (newPosition.X > ActiveDocument.Width
+                || newPosition.Y > ActiveDocument.Height
+                || newPosition.X < 0 || newPosition.Y < 0)
             {
-                ActiveDocument.PreviewLayer.Reset();
+                previewLayer.Reset();
                 previewLayerSize = -1;
             }
-        }
-
-        private bool IsInsideBounds(IEnumerable<Coordinates> highlightArea)
-        {
-            Coordinates start = highlightArea.First();
-            Coordinates end = highlightArea.Last();
-            return start.X <= ActiveDocument.Width - 1 &&
-                    start.Y <= ActiveDocument.Height - 1 &&
-                   end.X >= 0 && end.Y >= 0;
-        }
+        }
+
+        private void AdjustOffset(Coordinates newPosition, Layer previewLayer)
+        {
+            Coordinates start = newPosition - halfSize;
+            previewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
+        }
     }
 }

+ 2 - 5
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -1,4 +1,4 @@
-using PixiEditor.Helpers.Extensions;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
@@ -66,13 +66,12 @@ namespace PixiEditor.Models.DataHolders
         public void FlipActiveDocument(FlipType flip)
         {
             object[] processArgs = { flip };
-            object[] reverseProcessArgs = { flip == FlipType.Horizontal ? FlipType.Vertical : FlipType.Horizontal };
 
             FlipDocumentProcess(processArgs);
 
             UndoManager.AddUndoChange(new Change(
                 FlipDocumentProcess,
-                reverseProcessArgs,
+                processArgs,
                 FlipDocumentProcess,
                 processArgs,
                 $"Flip layer: {flip}"));
@@ -86,7 +85,6 @@ namespace PixiEditor.Models.DataHolders
                 using (new SKAutoCanvasRestore(layer.LayerBitmap.SkiaSurface.Canvas, true))
                 {
                     var copy = layer.LayerBitmap.SkiaSurface.Snapshot();
-                    layer.LayerBitmap.SkiaSurface.Canvas.Clear();
 
                     var canvas = layer.LayerBitmap.SkiaSurface.Canvas;
 
@@ -148,7 +146,6 @@ namespace PixiEditor.Models.DataHolders
                 using (new SKAutoCanvasRestore(layer.LayerBitmap.SkiaSurface.Canvas, true))
                 {
                     var copy = layer.LayerBitmap.SkiaSurface.Snapshot();
-                    layer.LayerBitmap.SkiaSurface.Canvas.Clear();
 
                     double radians = Math.PI * degrees / 180;
                     float sine = (float)Math.Abs(Math.Sin(radians));

+ 247 - 0
PixiEditor/Models/ImageManipulation/ShapeCalculator.cs

@@ -0,0 +1,247 @@
+using PixiEditor.Models.Position;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PixiEditor.Models.ImageManipulation
+{
+    public static class ShapeCalculator
+    {
+        public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
+            List<Coordinates> output)
+        {
+
+            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
+
+            CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, output);
+
+            if(fill)
+            {
+                CalculateFillForEllipse(output);
+            }
+        }
+
+        public static void GenerateRectangleNonAlloc(
+            Coordinates start,
+            Coordinates end, bool fill, int thickness, List<Coordinates> output)
+        {
+            DoubleCords fixedCoordinates = CalculateCoordinatesForShapeRotation(start, end);
+            CalculateRectanglePoints(fixedCoordinates, output);
+
+            for (int i = 1; i < (int)Math.Floor(thickness / 2f) + 1; i++)
+            {
+                CalculateRectanglePoints(
+                    new DoubleCords(
+                    new Coordinates(fixedCoordinates.Coords1.X - i, fixedCoordinates.Coords1.Y - i),
+                    new Coordinates(fixedCoordinates.Coords2.X + i, fixedCoordinates.Coords2.Y + i)), output);
+            }
+
+            for (int i = 1; i < (int)Math.Ceiling(thickness / 2f); i++)
+            {
+                CalculateRectanglePoints(
+                    new DoubleCords(
+                    new Coordinates(fixedCoordinates.Coords1.X + i, fixedCoordinates.Coords1.Y + i),
+                    new Coordinates(fixedCoordinates.Coords2.X - i, fixedCoordinates.Coords2.Y - i)), output);
+            }
+
+            if (fill)
+            {
+                CalculateRectangleFillNonAlloc(start, end, thickness, output);
+            }
+        }
+
+        public static DoubleCords CalculateCoordinatesForShapeRotation(
+            Coordinates startingCords,
+            Coordinates secondCoordinates)
+        {
+            Coordinates currentCoordinates = secondCoordinates;
+
+            if (startingCords.X > currentCoordinates.X && startingCords.Y > currentCoordinates.Y)
+            {
+                return new DoubleCords(
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y),
+                    new Coordinates(startingCords.X, startingCords.Y));
+            }
+
+            if (startingCords.X < currentCoordinates.X && startingCords.Y < currentCoordinates.Y)
+            {
+                return new DoubleCords(
+                    new Coordinates(startingCords.X, startingCords.Y),
+                    new Coordinates(currentCoordinates.X, currentCoordinates.Y));
+            }
+
+            if (startingCords.Y > currentCoordinates.Y)
+            {
+                return new DoubleCords(
+                    new Coordinates(startingCords.X, currentCoordinates.Y),
+                    new Coordinates(currentCoordinates.X, startingCords.Y));
+            }
+
+            if (startingCords.X > currentCoordinates.X && startingCords.Y <= currentCoordinates.Y)
+            {
+                return new DoubleCords(
+                    new Coordinates(currentCoordinates.X, startingCords.Y),
+                    new Coordinates(startingCords.X, currentCoordinates.Y));
+            }
+
+            return new DoubleCords(startingCords, secondCoordinates);
+        }
+
+        private static void CalculateFillForEllipse(List<Coordinates> outlineCoordinates)
+        {
+            if (!outlineCoordinates.Any())
+            {
+                return;
+            }
+
+            int bottom = outlineCoordinates.Max(x => x.Y);
+            int top = outlineCoordinates.Min(x => x.Y);
+            for (int i = top + 1; i < bottom; i++)
+            {
+                IEnumerable<Coordinates> rowCords = outlineCoordinates.Where(x => x.Y == i);
+                int right = rowCords.Max(x => x.X);
+                int left = rowCords.Min(x => x.X);
+                for (int j = left + 1; j < right; j++)
+                {
+                    outlineCoordinates.Add(new Coordinates(j, i));
+                }
+            }
+        }
+
+
+        /// <summary>
+        ///     Calculates ellipse points for specified coordinates and thickness.
+        /// </summary>
+        /// <param name="startCoordinates">Top left coordinate of ellipse.</param>
+        /// <param name="endCoordinates">Bottom right coordinate of ellipse.</param>
+        private static void CreateEllipse(Coordinates startCoordinates, Coordinates endCoordinates, List<Coordinates> output)
+        {
+            double radiusX = (endCoordinates.X - startCoordinates.X) / 2.0;
+            double radiusY = (endCoordinates.Y - startCoordinates.Y) / 2.0;
+            double centerX = (startCoordinates.X + endCoordinates.X + 1) / 2.0;
+            double centerY = (startCoordinates.Y + endCoordinates.Y + 1) / 2.0;
+
+            MidpointEllipse(radiusX, radiusY, centerX, centerY, output);
+        }
+
+        private static void MidpointEllipse(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> output)
+        {
+            if (halfWidth < 1 || halfHeight < 1)
+            {
+                FallbackRectangle(halfWidth, halfHeight, centerX, centerY, output);
+            }
+
+            // ellipse formula: halfHeight^2 * x^2 + halfWidth^2 * y^2 - halfHeight^2 * halfWidth^2 = 0
+
+            // Make sure we are always at the center of a pixel
+            double currentX = Math.Ceiling(centerX - 0.5) + 0.5;
+            double currentY = centerY + halfHeight;
+
+            double currentSlope;
+
+            // from PI/2 to middle
+            do
+            {
+                GetRegionPoints(currentX, centerX, currentY, centerY, output);
+
+                // calculate next pixel coords
+                currentX++;
+
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY - 0.5, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) >= 0)
+                {
+                    currentY--;
+                }
+
+                // calculate how far we've advanced
+                double derivativeX = 2 * Math.Pow(halfHeight, 2) * (currentX - centerX);
+                double derivativeY = 2 * Math.Pow(halfWidth, 2) * (currentY - centerY);
+                currentSlope = -(derivativeX / derivativeY);
+            }
+            while (currentSlope > -1 && currentY - centerY > 0.5);
+
+            // from middle to 0
+            while (currentY - centerY >= 0)
+            {
+                GetRegionPoints(currentX, centerX, currentY, centerY, output);
+
+                currentY--;
+                if ((Math.Pow(halfHeight, 2) * Math.Pow(currentX - centerX + 0.5, 2)) +
+                    (Math.Pow(halfWidth, 2) * Math.Pow(currentY - centerY, 2)) -
+                    (Math.Pow(halfWidth, 2) * Math.Pow(halfHeight, 2)) < 0)
+                {
+                    currentX++;
+                }
+            }
+        }
+
+        private static void FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY, List<Coordinates> output)
+        {
+            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
+            {
+                output.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
+                output.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
+            }
+
+            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
+            {
+                output.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
+                output.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
+            }
+        }
+
+        private static void GetRegionPoints(double x, double xc, double y, double yc, List<Coordinates> output)
+        {
+            output.Add(new Coordinates((int)Math.Floor(x), (int)Math.Floor(y)));
+            output.Add(new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(y)));
+            output.Add(new Coordinates((int)Math.Floor(x), (int)Math.Floor(-(y - yc) + yc)));
+            output.Add(new Coordinates((int)Math.Floor(-(x - xc) + xc), (int)Math.Floor(-(y - yc) + yc)));
+        }
+
+        private static void CalculateRectangleFillNonAlloc(Coordinates start, Coordinates end, int thickness, List<Coordinates> output)
+        {
+            int offset = (int)Math.Ceiling(thickness / 2f);
+            DoubleCords fixedCords = CalculateCoordinatesForShapeRotation(start, end);
+
+            DoubleCords innerCords = new DoubleCords
+            {
+                Coords1 = new Coordinates(fixedCords.Coords1.X + offset, fixedCords.Coords1.Y + offset),
+                Coords2 = new Coordinates(fixedCords.Coords2.X - (offset - 1), fixedCords.Coords2.Y - (offset - 1))
+            };
+
+            int height = innerCords.Coords2.Y - innerCords.Coords1.Y;
+            int width = innerCords.Coords2.X - innerCords.Coords1.X;
+
+            if (height < 1 || width < 1)
+            {
+                return;
+            }
+
+            int i = 0;
+            for (int y = 0; y < height; y++)
+            {
+                for (int x = 0; x < width; x++)
+                {
+                    output.Add(new Coordinates(innerCords.Coords1.X + x, innerCords.Coords1.Y + y));
+                    i++;
+                }
+            }
+        }
+
+        private static void CalculateRectanglePoints(DoubleCords coordinates, List<Coordinates> output)
+        {
+            for (int i = coordinates.Coords1.X; i < coordinates.Coords2.X + 1; i++)
+            {
+                output.Add(new Coordinates(i, coordinates.Coords1.Y));
+                output.Add(new Coordinates(i, coordinates.Coords2.Y));
+            }
+
+            for (int i = coordinates.Coords1.Y + 1; i <= coordinates.Coords2.Y - 1; i++)
+            {
+                output.Add(new Coordinates(coordinates.Coords1.X, i));
+                output.Add(new Coordinates(coordinates.Coords2.X, i));
+            }
+        }
+    }
+}

+ 5 - 0
PixiEditor/Models/Position/Coordinates.cs

@@ -73,5 +73,10 @@ namespace PixiEditor.Models.Position
         {
             return HashCode.Combine(X, Y);
         }
+
+        public SKPoint ToSKPoint()
+        {
+            return new SKPoint(X, Y);
+        }
     }
 }

+ 8 - 1
PixiEditor/Models/Tools/Tools/FloodFillTool.cs

@@ -26,7 +26,14 @@ namespace PixiEditor.Models.Tools.Tools
         {
             Stopwatch sw = new Stopwatch();
             sw.Start();
-            LinearFill(layer, coordinates[0], color);
+            if (layer.IsReset)
+            {
+                layer.LayerBitmap.SkiaSurface.Canvas.Clear(color);
+            }
+            else
+            {
+                LinearFill(layer, coordinates[0], color);
+            }
             sw.Stop();
             Trace.WriteLine(sw.ElapsedMilliseconds);
         }

+ 27 - 20
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -21,6 +21,7 @@ namespace PixiEditor.Models.Tools.Tools
         private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };
         private Coordinates[] lastChangedPixels = new Coordinates[3];
         private byte changedPixelsindex;
+        private Coordinates lastChangedPixel = new Coordinates(-1, -1);
 
         private BitmapManager BitmapManager { get; }
 
@@ -69,38 +70,44 @@ namespace PixiEditor.Models.Tools.Tools
             SKBlendMode blendMode = SKBlendMode.Src)
         {
 
-            SKStrokeCap cap = toolSize == 1 ? SKStrokeCap.Square : SKStrokeCap.Round;
+            SKStrokeCap cap = toolSize == 1 || toolSize == 3 ? SKStrokeCap.Square : SKStrokeCap.Round;
             if (!pixelPerfect)
             {
                 lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
                 return;
             }
 
-            if (previewLayer != null && previewLayer.GetPixelWithOffset(latestCords.X, latestCords.Y).Alpha > 0)
+            if (latestCords != lastChangedPixel)
             {
-                confirmedPixels.Add(latestCords);
-            }
-
-            lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
-            SetPixelToCheck(LineTool.GetBresenhamLine(startingCoords, latestCords));
+                if (previewLayer != null && previewLayer.GetPixelWithOffset(latestCords.X, latestCords.Y).Alpha > 0)
+                {
+                    confirmedPixels.Add(latestCords);
+                }
 
-            if (changedPixelsindex == 2)
-            {
-                byte alpha = ApplyPixelPerfectToPixels(
-                    layer,
-                    lastChangedPixels[0],
-                    lastChangedPixels[1],
-                    lastChangedPixels[2],
-                    color,
-                    toolSize,
-                    paint);
+                lineTool.DrawLine(layer, startingCoords, latestCords, color, toolSize, blendMode, cap);
+                SetPixelToCheck(LineTool.GetBresenhamLine(startingCoords, latestCords));
 
-                MovePixelsToCheck(alpha);
+                if (changedPixelsindex == 2)
+                {
+                    byte alpha = ApplyPixelPerfectToPixels(
+                        layer,
+                        lastChangedPixels[0],
+                        lastChangedPixels[1],
+                        lastChangedPixels[2],
+                        color,
+                        toolSize,
+                        paint);
+
+                    MovePixelsToCheck(alpha);
+
+                    lastChangedPixel = latestCords;
+                    return;
+                }
 
-                return;
+                changedPixelsindex += changedPixelsindex >= 2 ? (byte)0 : (byte)1;
             }
 
-            changedPixelsindex += changedPixelsindex >= 2 ? (byte)0 : (byte)1;
+            lastChangedPixel = latestCords;
         }

 

         private void MovePixelsToCheck(byte alpha)

+ 9 - 7
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -9,6 +9,7 @@ using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
@@ -66,17 +67,18 @@ namespace PixiEditor.Models.Tools.Tools
 
         public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
         {
-            //List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
-            //selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
-            return Array.Empty<Coordinates>(); //selection;
+            List<Coordinates> result = new List<Coordinates>();
+            ShapeCalculator.GenerateRectangleNonAlloc(
+                start, end, true, 1, result);
+            return result;
         }
 
         public IEnumerable<Coordinates> GetCircleSelectionForPoints(Coordinates start, Coordinates end)
         {
-            //DoubleCords fixedCoordinates = ShapeTool.CalculateCoordinatesForShapeRotation(start, end);
-            //List<Coordinates> selection = circleTool.CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, 1).ToList();
-            //selection.AddRange(circleTool.CalculateFillForEllipse(selection));
-            return Array.Empty<Coordinates>(); //selection;
+            List<Coordinates> result = new List<Coordinates>();
+            ShapeCalculator.GenerateEllipseNonAlloc(
+                start, end, true, result);
+            return result;
         }
 
         /// <summary>

+ 1 - 0
PixiEditor/PixiEditor.csproj

@@ -68,6 +68,7 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <DebugType>full</DebugType>
     <DebugSymbols>true</DebugSymbols>
+    <WarningLevel>0</WarningLevel>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">

+ 2 - 2
PixiEditor/Properties/AssemblyInfo.cs

@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.6.0")]
-[assembly: AssemblyFileVersion("0.1.6.0")]
+[assembly: AssemblyVersion("0.1.7.0")]
+[assembly: AssemblyFileVersion("0.1.7.0")]

+ 1 - 1
PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -36,7 +36,7 @@
                             BorderThickness="1" BorderBrush="Black"
                             Background="{StaticResource MainColor}"
                             Margin="5, 0, 10, 0">
-                        <Image Source="{Binding Layer.LayerBitmap, ElementName=uc}" Stretch="Uniform" Width="25" Height="25" 
+                        <local1:PlainLayerView TargetLayer="{Binding Layer, ElementName=uc}" Width="25" Height="25" 
                        RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                     </Border>
                     <Image Margin="0 0 5 0" Width="20" Source="/Images/Layer-add.png"  Visibility="{Binding Layer, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>