Skip to content

Commit

Permalink
Fixed magic wand tool
Browse files Browse the repository at this point in the history
  • Loading branch information
flabbet committed Nov 23, 2021
1 parent f2fcb7b commit ce09ad0
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,107 @@
using PixiEditor.Helpers.Extensions;
using PixiEditor.Models.Layers;
using PixiEditor.Models.Position;
using PixiEditor.Models.Tools;
using PixiEditor.Models.Tools.Tools;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace PixiEditor.Models.ImageManipulation
{
public static class ShapeCalculator
public static class ToolCalculator
{
public static void GetLinearFill(Layer layer, Coordinates startingCoords, int maxWidth, int maxHeight, SKColor newColor, List<Coordinates> output)
{
Queue<FloodFillRange> floodFillQueue = new Queue<FloodFillRange>();
SKColor colorToReplace = layer.GetPixelWithOffset(startingCoords.X, startingCoords.Y);
if ((colorToReplace.Alpha == 0 && newColor.Alpha == 0) ||
colorToReplace == newColor)
return;

int width = maxWidth;
int height = maxHeight;
if (startingCoords.X < 0 || startingCoords.Y < 0 || startingCoords.X >= width || startingCoords.Y >= height)
return;
var visited = new bool[width * height];

Int32Rect dirtyRect = new Int32Rect(startingCoords.X, startingCoords.Y, 1, 1);

PerformLinearFill(layer, floodFillQueue, startingCoords, width, colorToReplace, ref dirtyRect, visited, output);
PerformFloodFIll(layer, floodFillQueue, colorToReplace, ref dirtyRect, width, height, visited, output);
}

private static void PerformLinearFill(
Layer layer, Queue<FloodFillRange> floodFillQueue,
Coordinates coords, int width, SKColor colorToReplace, ref Int32Rect dirtyRect, bool[] visited, List<Coordinates> output)
{
// Find the Left Edge of the Color Area
int fillXLeft = coords.X;
while (true)
{
// Indicate that this pixel has been checked
int pixelIndex = (coords.Y * width) + fillXLeft;
visited[pixelIndex] = true;

// Move one pixel to the left
fillXLeft--;
// Exit the loop if we're at edge of the bitmap or the color area
if (fillXLeft < 0 || visited[pixelIndex - 1] || layer.GetPixelWithOffset(fillXLeft, coords.Y) != colorToReplace)
break;
}
int lastCheckedPixelLeft = fillXLeft + 1;

// Find the Right Edge of the Color Area
int fillXRight = coords.X;
while (true)
{
int pixelIndex = (coords.Y * width) + fillXRight;
visited[pixelIndex] = true;

fillXRight++;
if (fillXRight >= width || visited[pixelIndex + 1] || layer.GetPixelWithOffset(fillXRight, coords.Y) != colorToReplace)
break;
}
int lastCheckedPixelRight = fillXRight - 1;

int relativeY = coords.Y - layer.OffsetY;
LineTool.CalculateBresenhamLine(new Coordinates(lastCheckedPixelLeft - layer.OffsetX, relativeY), new Coordinates(lastCheckedPixelRight - layer.OffsetX, relativeY), output);
dirtyRect = dirtyRect.Expand(new Int32Rect(lastCheckedPixelLeft, coords.Y, lastCheckedPixelRight - lastCheckedPixelLeft + 1, 1));

FloodFillRange range = new FloodFillRange(lastCheckedPixelLeft, lastCheckedPixelRight, coords.Y);
floodFillQueue.Enqueue(range);
}

private static void PerformFloodFIll(
Layer layer, Queue<FloodFillRange> floodFillQueue,
SKColor colorToReplace, ref Int32Rect dirtyRect, int width, int height, bool[] pixelsVisited, List<Coordinates> output)
{
while (floodFillQueue.Count > 0)
{
FloodFillRange range = floodFillQueue.Dequeue();

//START THE LOOP UPWARDS AND DOWNWARDS
int upY = range.Y - 1; //so we can pass the y coord by ref
int downY = range.Y + 1;
int downPixelxIndex = (width * (range.Y + 1)) + range.StartX;
int upPixelIndex = (width * (range.Y - 1)) + range.StartX;
for (int i = range.StartX; i <= range.EndX; i++)
{
//START LOOP UPWARDS
//if we're not above the top of the bitmap and the pixel above this one is within the color tolerance
if (range.Y > 0 && (!pixelsVisited[upPixelIndex]) && layer.GetPixelWithOffset(i, upY) == colorToReplace)
PerformLinearFill(layer, floodFillQueue, new Coordinates(i, upY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
//START LOOP DOWNWARDS
if (range.Y < (height - 1) && (!pixelsVisited[downPixelxIndex]) && layer.GetPixelWithOffset(i, downY) == colorToReplace)
PerformLinearFill(layer, floodFillQueue, new Coordinates(i, downY), width, colorToReplace, ref dirtyRect, pixelsVisited, output);
downPixelxIndex++;
upPixelIndex++;
}
}
}

public static void GenerateEllipseNonAlloc(Coordinates start, Coordinates end, bool fill,
List<Coordinates> output)
{
Expand Down
20 changes: 12 additions & 8 deletions PixiEditor/Models/Tools/Tools/LineTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,23 @@ public class LineTool : ShapeTool
private SKPaint paint = new SKPaint() { Style = SKPaintStyle.Stroke };

public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates end)
{
List<Coordinates> output = new List<Coordinates>();
CalculateBresenhamLine(start, end, output);
return output;
}

public static void CalculateBresenhamLine(Coordinates start, Coordinates end, List<Coordinates> output)
{
int x1 = start.X;
int x2 = end.X;
int y1 = start.Y;
int y2 = end.Y;

List<Coordinates> coordinates = new List<Coordinates>();
if (x1 == x2 && y1 == y2)
{
coordinates.Add(start);
return coordinates;
output.Add(start);
return;
}

int d, dx, dy, ai, bi, xi, yi;
Expand Down Expand Up @@ -55,7 +61,7 @@ public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates
dy = y1 - y2;
}

coordinates.Add(new Coordinates(x, y));
output.Add(new Coordinates(x, y));

if (dx > dy)
{
Expand All @@ -77,7 +83,7 @@ public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates
x += xi;
}

coordinates.Add(new Coordinates(x, y));
output.Add(new Coordinates(x, y));
}
}
else
Expand All @@ -100,11 +106,9 @@ public static List<Coordinates> GetBresenhamLine(Coordinates start, Coordinates
y += yi;
}

coordinates.Add(new Coordinates(x, y));
output.Add(new Coordinates(x, y));
}
}

return coordinates;
}

public LineTool()
Expand Down
24 changes: 14 additions & 10 deletions PixiEditor/Models/Tools/Tools/MagicWandTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ namespace PixiEditor.Models.Tools.Tools
{
public class MagicWandTool : ReadonlyTool, ICachedDocumentTool
{
private readonly FloodFillTool floodFill;

private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }

private BitmapManager BitmapManager { get; }

private IEnumerable<Coordinates> oldSelection;
private List<Coordinates> newSelection = new List<Coordinates>();

public override string Tooltip => "Magic Wand (W). Flood's the selection";

Expand Down Expand Up @@ -56,20 +55,25 @@ public override void OnRecordingLeftMouseDown(MouseEventArgs e)

Selection selection = BitmapManager.ActiveDocument.ActiveSelection;

/*selection.SetSelection(
floodFill.LinearFill(
layer,
new Coordinates((int)document.MouseXOnCanvas, (int)document.MouseYOnCanvas),
SKColors.White
).ChangedPixels.Keys,
selectionType);*/
newSelection.Clear();

ToolCalculator.GetLinearFill(
layer,
new Coordinates(
(int)document.MouseXOnCanvas,
(int)document.MouseYOnCanvas),
BitmapManager.ActiveDocument.Width,
BitmapManager.ActiveDocument.Height,
SKColors.White,
newSelection);

selection.SetSelection(newSelection, selectionType);

SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelection, selectionType);
}

public MagicWandTool(BitmapManager manager)
{
floodFill = new FloodFillTool(manager);
BitmapManager = manager;

Toolbar = new MagicWandToolbar();
Expand Down
4 changes: 2 additions & 2 deletions PixiEditor/Models/Tools/Tools/SelectTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ public override void Use(List<Coordinates> pixels)
public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
{
List<Coordinates> result = new List<Coordinates>();
ShapeCalculator.GenerateRectangleNonAlloc(
ToolCalculator.GenerateRectangleNonAlloc(
start, end, true, 1, result);
return result;
}

public IEnumerable<Coordinates> GetCircleSelectionForPoints(Coordinates start, Coordinates end)
{
List<Coordinates> result = new List<Coordinates>();
ShapeCalculator.GenerateEllipseNonAlloc(
ToolCalculator.GenerateEllipseNonAlloc(
start, end, true, result);
return result;
}
Expand Down

0 comments on commit ce09ad0

Please sign in to comment.