Browse Source

Reimplement the brightness tool in the old slow way

Equbuxu 2 years ago
parent
commit
887fcf85bb

+ 1 - 1
src/ChunkyImageLib/ChunkyImage.cs

@@ -306,7 +306,7 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    internal bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    public bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
     {
         lock (lockObject)
         {

+ 36 - 8
src/ChunkyImageLib/ChunkyImageEx.cs

@@ -8,16 +8,44 @@ namespace ChunkyImageLib;
 public static class IReadOnlyChunkyImageEx
 {
     /// <summary>
-    ///     Draws the image onto the passed surface.
+    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
+    /// The region is taken from the most up to date version of the <see cref="ChunkyImage"/>
     /// </summary>
-    /// <param name="image">Image to draw.</param>
-    /// <param name="fullResRegion">A region inside an chunky image.</param>
-    /// <param name="resolution">Chunk resolution.</param>
-    /// <param name="surface">Surface to draw onto.</param>
-    /// <param name="pos">Starting position on the surface.</param>
-    /// <param name="paint">Paint that is used to draw.</param>
+    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
+    /// <param name="fullResRegion">The region to extract</param>
+    /// <param name="resolution">Chunk resolution</param>
+    /// <param name="surface">Surface to draw onto</param>
+    /// <param name="pos">Starting position on the surface</param>
+    /// <param name="paint">Paint to use for drawing</param>
     public static void DrawMostUpToDateRegionOn
         (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    {
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawMostUpToDateChunkOn, paint);
+    }
+    
+    /// <summary>
+    /// Extracts a region from the <see cref="ChunkyImage"/> and draws it onto the passed <see cref="DrawingSurface"/>.
+    /// The region is taken from the committed version of the <see cref="ChunkyImage"/>
+    /// </summary>
+    /// <param name="image"><see cref="ChunkyImage"/> to extract the region from</param>
+    /// <param name="fullResRegion">The region to extract</param>
+    /// <param name="resolution">Chunk resolution</param>
+    /// <param name="surface">Surface to draw onto</param>
+    /// <param name="pos">Starting position on the surface</param>
+    /// <param name="paint">Paint to use for drawing</param>
+    public static void DrawCommittedRegionOn
+        (this IReadOnlyChunkyImage image, RectI fullResRegion, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null)
+    {
+        DrawRegionOn(fullResRegion, resolution, surface, pos, image.DrawCommittedChunkOn, paint);
+    }
+    
+    private static void DrawRegionOn(
+        RectI fullResRegion,
+        ChunkResolution resolution,
+        DrawingSurface surface,
+        VecI pos,
+        Func<VecI, ChunkResolution, DrawingSurface, VecI, Paint?, bool> drawingFunc,
+        Paint? paint = null)
     {
         surface.Canvas.Save();
         surface.Canvas.ClipRect(RectD.Create(pos, fullResRegion.Size));
@@ -32,7 +60,7 @@ public static class IReadOnlyChunkyImageEx
             for (int i = chunkTopLeft.X; i <= chunkBotRight.X; i++)
             {
                 var chunkPos = new VecI(i, j);
-                image.DrawMostUpToDateChunkOn(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint);
+                drawingFunc(chunkPos, resolution, surface, offsetTargetRes + (chunkPos - chunkTopLeft) * resolution.PixelSize() + pos, paint);
             }
         }
 

+ 1 - 0
src/ChunkyImageLib/IReadOnlyChunkyImage.cs

@@ -9,6 +9,7 @@ namespace ChunkyImageLib;
 public interface IReadOnlyChunkyImage
 {
     bool DrawMostUpToDateChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
+    bool DrawCommittedChunkOn(VecI chunkPos, ChunkResolution resolution, DrawingSurface surface, VecI pos, Paint? paint = null);
     RectI? FindLatestBounds();
     Color GetCommittedPixel(VecI posOnImage);
     Color GetMostUpToDatePixel(VecI posOnImage);

+ 42 - 1
src/ChunkyImageLib/Operations/EllipseHelper.cs

@@ -4,7 +4,10 @@ using PixiEditor.DrawingApi.Core.Numerics;
 namespace ChunkyImageLib.Operations;
 public class EllipseHelper
 {
-    public static (List<VecI> lines, RectI rect) SplitEllipseIntoRegions(List<VecI> ellipse, RectI ellipseBounds)
+    /// <summary>
+    /// Separates the ellipse's inner area into a bunch of horizontal lines and one big rectangle for drawing.
+    /// </summary>
+    public static (List<VecI> lines, RectI rect) SplitEllipseFillIntoRegions(IReadOnlyList<VecI> ellipse, RectI ellipseBounds)
     {
         if (ellipse.Count == 0)
             return (new(), RectI.Empty);
@@ -49,6 +52,44 @@ public class EllipseHelper
         }
         return (lines, inscribedRect);
     }
+    
+    /// <summary>
+    /// Splits the ellipse into a bunch of horizontal lines.
+    /// The resulting list contains consecutive pairs of <see cref="VecI"/>s, each pair has one for the start of the line and one for the end.
+    /// </summary>
+    public static List<VecI> SplitEllipseIntoLines(List<VecI> ellipse)
+    {
+        List<VecI> lines = new();
+        var sorted = ellipse.OrderBy(
+            a => a,
+            Comparer<VecI>.Create((a, b) => a.Y != b.Y ? a.Y - b.Y : a.X - b.X)
+        );
+
+        int minX = int.MaxValue;
+        int maxX = int.MinValue;
+        VecI? prev = null;
+        foreach (var point in sorted)
+        {
+            if (prev.HasValue && point.Y != prev.Value.Y)
+            {
+                int prevY = prev.Value.Y;
+                lines.Add(new(minX, prevY));
+                lines.Add(new(maxX, prevY));
+                minX = int.MaxValue;
+                maxX = int.MinValue;
+            }
+            minX = Math.Min(point.X, minX);
+            maxX = Math.Max(point.X, maxX);
+            prev = point;
+        }
+        if (prev != null)
+        {
+            lines.Add(new(minX, prev.Value.Y));
+            lines.Add(new(maxX, prev.Value.Y));
+        }
+        return lines;
+    }
+    
     public static List<VecI> GenerateEllipseFromRect(RectI rect)
     {
         if (rect.IsZeroOrNegativeArea)

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

@@ -40,7 +40,7 @@ internal class EllipseOperation : IDrawOperation
             ellipse = ellipseList.Select(a => new Point(a)).ToArray();
             if (fillColor.A > 0 || paint.BlendMode != BlendMode.SrcOver)
             {
-                (var fill, ellipseFillRect) = EllipseHelper.SplitEllipseIntoRegions(ellipseList, location);
+                (var fill, ellipseFillRect) = EllipseHelper.SplitEllipseFillIntoRegions(ellipseList, location);
                 ellipseFill = fill.Select(a => new Point(a)).ToArray();
             }
         }

+ 47 - 20
src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs

@@ -1,4 +1,5 @@
-using PixiEditor.DrawingApi.Core.ColorsImpl;
+using ChunkyImageLib.Operations;
+using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
@@ -8,33 +9,33 @@ namespace PixiEditor.ChangeableDocument.Changes.Drawing;
 internal class ChangeBrightness_UpdateableChange : UpdateableChange
 {
     private readonly Guid layerGuid;
+    private readonly float correctionFactor;
     private readonly int strokeWidth;
     private readonly List<VecI> positions = new();
     private bool ignoreUpdate = false;
     private readonly bool repeat;
-    private readonly bool darken;
-    private readonly Paint paint;
-    private readonly Color color;
+    
+    private Surface tempSurface;
+    private List<VecI> ellipseLines;
+    
     private CommittedChunkStorage? savedChunks;
 
     [GenerateUpdateableChangeActions]
-    public ChangeBrightness_UpdateableChange(Guid layerGuid, VecI pos, float correctionFactor, int strokeWidth, bool repeat, bool darken)
+    public ChangeBrightness_UpdateableChange(Guid layerGuid, VecI pos, float correctionFactor, int strokeWidth, bool repeat)
     {
         this.layerGuid = layerGuid;
+        this.correctionFactor = correctionFactor;
         this.strokeWidth = strokeWidth;
-        this.positions.Add(pos);
         this.repeat = repeat;
-        this.darken = darken;
 
-        color = (darken ? Colors.Black : Colors.White)
-            .WithAlpha((byte)Math.Clamp(correctionFactor * 255 / 100, 0, 255)); 
-        paint = new Paint { BlendMode = repeat ? BlendMode.SrcOver : BlendMode.Src };
+        ellipseLines = EllipseHelper.SplitEllipseIntoLines((EllipseHelper.GenerateEllipseFromRect(new RectI(0, 0, strokeWidth, strokeWidth))));
+        tempSurface = new Surface(new VecI(strokeWidth, strokeWidth));
     }
 
     [UpdateChangeMethod]
     public void Update(VecI pos)
     {
-        ignoreUpdate = positions[^1] == pos;
+        ignoreUpdate = positions.Count > 0 && positions[^1] == pos;
         if (!ignoreUpdate)
             positions.Add(pos);
     }
@@ -45,7 +46,6 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
             return new Error();
         Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
         DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
-        layer.LayerImage.SetBlendMode(darken ? BlendMode.Multiply : BlendMode.Screen);
         return new Success();
     }
 
@@ -57,13 +57,43 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
 
         int queueLength = layer.LayerImage.QueueLength;
-        layer.LayerImage.EnqueueDrawEllipse(
-            new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
-            Colors.Transparent, color, 0, paint);
+        
+        ChangeBrightness(ellipseLines, strokeWidth, pos + new VecI(-strokeWidth / 2), correctionFactor, repeat, tempSurface, layer.LayerImage);
+        
         var affected = layer.LayerImage.FindAffectedChunks(queueLength);
         
         return new LayerImageChunks_ChangeInfo(layerGuid, affected);
     }
+    
+    private static void ChangeBrightness(
+        List<VecI> circleLines, int circleDiameter, VecI offset, float correctionFactor, bool repeat, Surface tempSurface, ChunkyImage layerImage)
+    {
+        tempSurface.DrawingSurface.Canvas.Clear();
+        if (repeat)
+        {
+            layerImage.DrawMostUpToDateRegionOn
+                (new RectI(offset, new(circleDiameter, circleDiameter)), ChunkResolution.Full, tempSurface.DrawingSurface, new VecI(0));
+        }
+        else
+        {
+            layerImage.DrawCommittedRegionOn
+                (new RectI(offset, new(circleDiameter, circleDiameter)), ChunkResolution.Full, tempSurface.DrawingSurface, new VecI(0));
+        }
+        
+        for (var i = 0; i < circleLines.Count - 1; i++)
+        {
+            VecI left = circleLines[i];
+            VecI right = circleLines[i + 1];
+            int y = left.Y;
+            
+            for (VecI pos = new VecI(left.X, y); pos.X <= right.X; pos.X++)
+            {
+                Color pixel = tempSurface.GetSRGBPixel(pos);
+                Color newColor = ColorHelper.ChangeColorBrightness(pixel, correctionFactor);
+                layerImage.EnqueueDrawPixel(pos + offset, newColor, BlendMode.Src);
+            }
+        }
+    }
 
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
     {
@@ -76,12 +106,9 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
         if (!firstApply)
         {
             DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
-            layer.LayerImage.SetBlendMode(darken ? BlendMode.Multiply : BlendMode.Screen);
             foreach (VecI pos in positions)
             {
-                layer.LayerImage.EnqueueDrawEllipse(
-                    new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
-                    Colors.Transparent, color, 0, paint);
+                ChangeBrightness(ellipseLines, strokeWidth, pos + new VecI(-strokeWidth / 2), correctionFactor, repeat, tempSurface, layer.LayerImage);
             }
         }
 
@@ -101,6 +128,6 @@ internal class ChangeBrightness_UpdateableChange : UpdateableChange
 
     public override void Dispose()
     {
-        paint.Dispose();
+        tempSurface.Dispose();
     }
 }

+ 2 - 2
src/PixiEditor/Models/Colors/ExColor.cs → src/PixiEditor.ChangeableDocument/ColorHelper.cs

@@ -1,8 +1,8 @@
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 
-namespace PixiEditor.Models.Colors;
+namespace PixiEditor.ChangeableDocument;
 
-internal static class ExColor
+public static class ColorHelper
 {
     /// <summary>
     ///     Creates color with corrected brightness.

+ 3 - 5
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/BrightnessToolExecutor.cs

@@ -11,7 +11,6 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
 {
     private Guid guidValue;
     private bool repeat;
-    private bool darken;
     private float correctionFactor;
     private int toolSize;
 
@@ -28,10 +27,9 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
         guidValue = member.GuidValue;
         repeat = tool.BrightnessMode == BrightnessMode.Repeat;
         toolSize = tool.ToolSize;
-        darken = tool.Darken;
-        correctionFactor = tool.CorrectionFactor;
+        correctionFactor = tool.Darken ? -tool.CorrectionFactor : tool.CorrectionFactor;
 
-        ChangeBrightness_Action action = new(guidValue, controller!.LastPixelPosition, correctionFactor, toolSize, repeat, darken);
+        ChangeBrightness_Action action = new(guidValue, controller!.LastPixelPosition, correctionFactor, toolSize, repeat);
         internals!.ActionAccumulator.AddActions(action);
 
         return ExecutionState.Success;
@@ -39,7 +37,7 @@ internal class BrightnessToolExecutor : UpdateableChangeExecutor
 
     public override void OnPixelPositionChange(VecI pos)
     {
-        ChangeBrightness_Action action = new(guidValue, pos, correctionFactor, toolSize, repeat, darken);
+        ChangeBrightness_Action action = new(guidValue, pos, correctionFactor, toolSize, repeat);
         internals!.ActionAccumulator.AddActions(action);
     }
 

+ 3 - 0
src/PixiEditor/PixiEditor.csproj

@@ -393,4 +393,7 @@
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Models\Colors" />
+  </ItemGroup>
 </Project>