Explorar o código

Implement brightness tool

Equbuxu %!s(int64=3) %!d(string=hai) anos
pai
achega
52c4f0e416

+ 2 - 2
src/ChunkyImageLib/ChunkyImage.cs

@@ -413,12 +413,12 @@ public class ChunkyImage : IReadOnlyChunkyImage, IDisposable
     }
 
     /// <exception cref="ObjectDisposedException">This image is disposed</exception>
-    public void EnqueueDrawEllipse(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth)
+    public void EnqueueDrawEllipse(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth, SKPaint? paint = null)
     {
         lock (lockObject)
         {
             ThrowIfDisposed();
-            EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth);
+            EllipseOperation operation = new(location, strokeColor, fillColor, strokeWidth, paint);
             EnqueueOperation(operation);
         }
     }

+ 6 - 5
src/ChunkyImageLib/Operations/EllipseOperation.cs

@@ -11,19 +11,20 @@ internal class EllipseOperation : IDrawOperation
     private readonly SKColor fillColor;
     private readonly int strokeWidth;
     private bool init = false;
-    private readonly SKPaint paint = new();
+    private readonly SKPaint paint;
     private SKPath? outerPath;
     private SKPath? innerPath;
     private SKPoint[]? ellipse;
     private SKPoint[]? ellipseFill;
     private RectI? ellipseFillRect;
 
-    public EllipseOperation(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth)
+    public EllipseOperation(RectI location, SKColor strokeColor, SKColor fillColor, int strokeWidth, SKPaint? paint = null)
     {
         this.location = location;
         this.strokeColor = strokeColor;
         this.fillColor = fillColor;
         this.strokeWidth = strokeWidth;
+        this.paint = paint?.Clone() ?? new SKPaint();
     }
 
     private void Init()
@@ -76,13 +77,13 @@ internal class EllipseOperation : IDrawOperation
             {
                 surf.Canvas.Save();
                 surf.Canvas.ClipPath(innerPath);
-                surf.Canvas.DrawColor(fillColor, SKBlendMode.SrcOver);
+                surf.Canvas.DrawColor(fillColor, paint.BlendMode);
                 surf.Canvas.Restore();
             }
             surf.Canvas.Save();
             surf.Canvas.ClipPath(outerPath);
             surf.Canvas.ClipPath(innerPath, SKClipOperation.Difference);
-            surf.Canvas.DrawColor(strokeColor, SKBlendMode.SrcOver);
+            surf.Canvas.DrawColor(strokeColor, paint.BlendMode);
             surf.Canvas.Restore();
         }
         surf.Canvas.Restore();
@@ -107,7 +108,7 @@ internal class EllipseOperation : IDrawOperation
             newLocation = newLocation.ReflectX((int)verAxisX);
         if (horAxisY is not null)
             newLocation = newLocation.ReflectY((int)horAxisY);
-        return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth);
+        return new EllipseOperation(newLocation, strokeColor, fillColor, strokeWidth, paint);
     }
 
     public void Dispose()

+ 103 - 0
src/PixiEditor.ChangeableDocument/Changes/Drawing/ChangeBrightness_UpdateableChange.cs

@@ -0,0 +1,103 @@
+using SkiaSharp;
+
+namespace PixiEditor.ChangeableDocument.Changes.Drawing;
+
+internal class ChangeBrightness_UpdateableChange : UpdateableChange
+{
+    private readonly Guid layerGuid;
+    private readonly int strokeWidth;
+    private readonly List<VecI> positions = new();
+    private bool ignoreUpdate = false;
+    private readonly bool repeat;
+    private readonly bool darken;
+    private readonly SKPaint paint;
+    private readonly SKColor color;
+    private CommittedChunkStorage? savedChunks;
+
+    [GenerateUpdateableChangeActions]
+    public ChangeBrightness_UpdateableChange(Guid layerGuid, VecI pos, float correctionFactor, int strokeWidth, bool repeat, bool darken)
+    {
+        this.layerGuid = layerGuid;
+        this.strokeWidth = strokeWidth;
+        this.positions.Add(pos);
+        this.repeat = repeat;
+        this.darken = darken;
+
+        color = (darken ? SKColors.Black : SKColors.White)
+            .WithAlpha((byte)Math.Clamp(correctionFactor * 255 / 100, 0, 255)); 
+        paint = new SKPaint { BlendMode = repeat ? SKBlendMode.SrcOver : SKBlendMode.Src };
+    }
+
+    [UpdateChangeMethod]
+    public void Update(VecI pos)
+    {
+        ignoreUpdate = positions[^1] == pos;
+        if (!ignoreUpdate)
+            positions.Add(pos);
+    }
+    
+    public override OneOf<Success, Error> InitializeAndValidate(Document target)
+    {
+        if (!DrawingChangeHelper.IsValidForDrawing(target, layerGuid, false))
+            return new Error();
+        Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
+        DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
+        layer.LayerImage.SetBlendMode(darken ? SKBlendMode.Multiply : SKBlendMode.Screen);
+        return new Success();
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        if (ignoreUpdate)
+            return new None();
+        VecI pos = positions[^1];
+        Layer layer = (Layer)target.FindMemberOrThrow(layerGuid);
+
+        int queueLength = layer.LayerImage.QueueLength;
+        layer.LayerImage.EnqueueDrawEllipse(
+            new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
+            SKColors.Transparent, color, 0, paint);
+        var affected = layer.LayerImage.FindAffectedChunks(queueLength);
+        
+        return new LayerImageChunks_ChangeInfo(layerGuid, affected);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply, out bool ignoreInUndo)
+    {
+        var layer = (Layer)target.FindMemberOrThrow(layerGuid);
+        ignoreInUndo = false;
+
+        if (savedChunks is not null)
+            throw new InvalidOperationException("Trying to apply while there are saved chunks");
+        
+        if (!firstApply)
+        {
+            DrawingChangeHelper.ApplyClipsSymmetriesEtc(target, layer.LayerImage, layerGuid, false);
+            layer.LayerImage.SetBlendMode(darken ? SKBlendMode.Multiply : SKBlendMode.Screen);
+            foreach (VecI pos in positions)
+            {
+                layer.LayerImage.EnqueueDrawEllipse(
+                    new RectI(pos + new VecI(-strokeWidth/2), new(strokeWidth)),
+                    SKColors.Transparent, color, 0, paint);
+            }
+        }
+
+        var affChunks = layer.LayerImage.FindAffectedChunks();
+        savedChunks = new CommittedChunkStorage(layer.LayerImage, affChunks);
+        layer.LayerImage.CommitChanges();
+        if (firstApply)
+            return new None();
+        return new LayerImageChunks_ChangeInfo(layerGuid, affChunks);
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target)
+    {
+        var affected = DrawingChangeHelper.ApplyStoredChunksDisposeAndSetToNull(target, layerGuid, false, ref savedChunks);
+        return new LayerImageChunks_ChangeInfo(layerGuid, affected);
+    }
+
+    public override void Dispose()
+    {
+        paint.Dispose();
+    }
+}

+ 1 - 0
src/PixiEditorPrototype/Models/Tool.cs

@@ -12,6 +12,7 @@ internal enum Tool
     Eraser,
     ShiftLayer,
     FloodFill,
+    Brightness,
     // selection
     SelectRectangle,
     SelectEllipse,

+ 27 - 0
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -177,6 +177,7 @@ internal class DocumentViewModel : INotifyPropertyChanged
     private bool drawingPathBasedPen = false;
     private bool drawingLineBasedPen = false;
     private bool drawingPixelPerfectPen = false;
+    private bool drawingBrightness = false;
     private bool transformingRectangle = false;
     private bool transformingEllipse = false;
     private bool shiftingLayer = false;
@@ -389,6 +390,32 @@ internal class DocumentViewModel : INotifyPropertyChanged
         Helpers.ActionAccumulator.AddFinishedActions(new EndPathBasedPen_Action());
     }
 
+    public void StartUpdateBrightness(VecI pos, int strokeWidth, float correctionFactor, bool repeat, bool darken)
+    {
+        if (!CanStartUpdate())
+            return;
+        updateableChangeActive = true;
+        drawingBrightness = true;
+        var member = FindFirstSelectedMember();
+        Helpers.ActionAccumulator.AddActions(
+            new ChangeBrightness_Action(
+                member!.GuidValue, 
+                pos, 
+                correctionFactor, 
+                strokeWidth, 
+                repeat, 
+                darken));
+    }
+
+    public void EndBrightness()
+    {
+        if (!drawingBrightness)
+            return;
+        drawingBrightness = false;
+        updateableChangeActive = false;
+        Helpers.ActionAccumulator.AddFinishedActions(new EndChangeBrightness_Action());
+    }
+    
     public void StartUpdateLineBasedPen(VecI pos, SKColor color, bool replacing = false)
     {
         if (!CanStartUpdate())

+ 12 - 0
src/PixiEditorPrototype/ViewModels/ViewModelMain.cs

@@ -31,6 +31,9 @@ internal class ViewModelMain : INotifyPropertyChanged
 
     public bool KeepOriginalImageOnTransform { get; set; } = false;
     public bool ReferenceAllLayers { get; set; } = false;
+    public bool BrightnessDarken { get; set; } = false;
+    public bool BrightnessRepeat { get; set; } = false;
+    public float BrightnessCorrectionFactor { get; set; } = 5f;
     public float StrokeWidth { get; set; } = 1f;
     public SKStrokeCap LineStrokeCap { get; set; } = SKStrokeCap.Butt;
 
@@ -180,6 +183,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.PixelPerfectPen:
                 ActiveDocument!.StartUpdatePixelPerfectPen((VecI)pos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
                 break;
+            case Tool.Brightness:
+                ActiveDocument!.StartUpdateBrightness((VecI)pos, (int)StrokeWidth, BrightnessCorrectionFactor, BrightnessRepeat, BrightnessDarken);
+                break;
             case Tool.Eraser:
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)pos, SKColors.Transparent, true);
                 break;
@@ -263,6 +269,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.PixelPerfectPen:
                 ActiveDocument!.StartUpdatePixelPerfectPen((VecI)canvasPos, new SKColor(SelectedColor.R, SelectedColor.G, SelectedColor.B, SelectedColor.A));
                 break;
+            case Tool.Brightness:
+                ActiveDocument!.StartUpdateBrightness((VecI)canvasPos, (int)StrokeWidth, BrightnessCorrectionFactor, BrightnessRepeat, BrightnessDarken);
+                break;
             case Tool.Eraser:
                 ActiveDocument!.StartUpdateLineBasedPen((VecI)canvasPos, SKColors.Transparent, true);
                 break;
@@ -320,6 +329,9 @@ internal class ViewModelMain : INotifyPropertyChanged
             case Tool.PixelPerfectPen:
                 ActiveDocument!.EndUPixelPerfectPen();
                 break;
+            case Tool.Brightness:
+                ActiveDocument!.EndBrightness();
+                break;
             case Tool.LineBasedPen:
             case Tool.Eraser:
                 ActiveDocument!.EndLineBasedPen();

+ 17 - 0
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -591,6 +591,16 @@
                     Command="{Binding ActiveDocument.ResizeImageCommand}">
                     Resize Image
                 </Button>
+                <CheckBox
+                    IsChecked="{Binding BrightnessRepeat}">
+                    Brgt Repeat
+                </CheckBox>
+                <CheckBox
+                    IsChecked="{Binding BrightnessDarken}">
+                    Brgt Darken
+                </CheckBox>
+                <Label>BrgtCorFact</Label>
+                <TextBox Text="{Binding BrightnessCorrectionFactor}"/>
             </WrapPanel>
         </Border>
         <Border
@@ -651,6 +661,13 @@
                     CommandParameter="{x:Static models:Tool.Eraser}">
                     Eraser
                 </Button>
+                <Button
+                    Width="70"
+                    Margin="5"
+                    Command="{Binding ChangeActiveToolCommand}"
+                    CommandParameter="{x:Static models:Tool.Brightness}">
+                    Brightness
+                </Button>
                 <Button
                     Width="70"
                     Margin="5"

+ 1 - 1
src/README.md

@@ -98,7 +98,7 @@ Decouples the state of a document from the UI.
         - [x] Regular pen
         - [x] Pixel-perfect pen
         - [x] Fill
-        - [ ] Brightness
+        - [x] Brightness
         - [x] Basic selection changes
         - [x] Selection modes
         - [x] Circular selection