Skip to content

Commit

Permalink
Merge branch 'quickfill' of github.com:PixiEditor/PixiEditor into qui…
Browse files Browse the repository at this point in the history
…ckfill
  • Loading branch information
CPKreu committed Nov 24, 2021
2 parents ce436dc + 4f1ce92 commit 0f18204
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 31 deletions.
29 changes: 26 additions & 3 deletions PixiEditor/Models/DataHolders/Document/Document.Operations.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using PixiEditor.Helpers.Extensions;
using PixiEditor.Models.Enums;
using PixiEditor.Models.Layers;
using PixiEditor.Models.Position;
using PixiEditor.Models.Undo;
using SkiaSharp;
using System;
Expand Down Expand Up @@ -87,19 +88,41 @@ private void FlipDocumentProcess(object[] processArgs)

var canvas = layer.LayerBitmap.SkiaSurface.Canvas;

canvas.Clear();
layer.ClipCanvas();

if (flip == FlipType.Horizontal)
{
canvas.Translate(layer.MaxWidth + layer.OffsetX, 0);
canvas.Translate(layer.Width, 0);
canvas.Scale(-1, 1, 0, 0);
}
else
{
canvas.Translate(0, layer.MaxHeight + layer.OffsetY);
canvas.Translate(0, layer.Width);
canvas.Scale(1, -1, 0, 0);
}

// Flip offset based on document and layer center point
var documentCenter = new Coordinates(Width / 2, Height / 2);
var layerCenter = new Coordinates(layer.Width / 2, layer.Height / 2);

int newOffsetX = layer.OffsetX;
int newOffsetY = layer.OffsetY;

if (flip == FlipType.Horizontal)
{
newOffsetX += layerCenter.X;
int diff = documentCenter.X - newOffsetX;
newOffsetX = layer.OffsetX + (diff * 2);
}
else if(flip == FlipType.Vertical)
{
newOffsetY += layerCenter.Y;
int diff = documentCenter.Y - newOffsetY;
newOffsetY = layer.OffsetY + (diff * 2);
}

layer.Offset = new Thickness(newOffsetX, newOffsetY, 0, 0);

canvas.DrawImage(copy, default(SKPoint));
copy.Dispose();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,118 @@
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
{
/// <summary>
/// This function calculates fill and outputs it into Coordinates list.
/// </summary>
/// <remarks>All coordinates are calculated without layer offset.
/// If you want to take consideration offset, just do it in CalculateBresenhamLine in PerformLinearFill function.</remarks>
/// <param name="layer">Layer to calculate fill from.</param>
/// <param name="startingCoords">Starting fill coordinates</param>
/// <param name="maxWidth">Maximum fill width</param>
/// <param name="maxHeight">Maximum fill height</param>
/// <param name="newColor">Replacement color to stop on</param>
/// <param name="output">List with output coordinates.</param>
public static void GetLinearFillAbsolute(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, relativeY), new Coordinates(lastCheckedPixelRight, 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
22 changes: 15 additions & 7 deletions PixiEditor/Models/Layers/Layer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,7 @@ public void DynamicResizeRelative(int newMaxX, int newMaxY, int newMinX, int new
}
}

/// <summary>
/// Changes size of bitmap to fit content.
/// </summary>
public void ClipCanvas()
public Int32Rect GetContentDimensions()
{
DoubleCords points = GetEdgePoints();
int smallestX = points.Coords1.X;
Expand All @@ -489,13 +486,24 @@ public void ClipCanvas()

if (smallestX < 0 && smallestY < 0 && biggestX < 0 && biggestY < 0)
{
return;
return Int32Rect.Empty;
}

int width = biggestX - smallestX + 1;
int height = biggestY - smallestY + 1;
ResizeCanvas(0, 0, smallestX, smallestY, width, height);
Offset = new Thickness(OffsetX + smallestX, OffsetY + smallestY, 0, 0);
return new Int32Rect(smallestX, smallestY, width, height);
}

/// <summary>
/// Changes size of bitmap to fit content.
/// </summary>
public void ClipCanvas()
{
var dimensions = GetContentDimensions();
if (dimensions == Int32Rect.Empty) return;

ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);
}

public void Reset()
Expand Down
5 changes: 5 additions & 0 deletions PixiEditor/Models/Position/Coordinates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public static implicit operator Coordinates((int width, int height) tuple)
return new Coordinates(coordiantes.X - size, coordiantes.Y - size);
}

public static Coordinates operator -(Coordinates coordiantes1, Coordinates coordinates2)
{
return new Coordinates(coordiantes1.X - coordinates2.X, coordiantes1.Y - coordinates2.Y);
}

public static bool operator ==(Coordinates c1, Coordinates c2)
{
return c2.X == c1.X && c2.Y == c1.Y;
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.GetLinearFillAbsolute(
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 0f18204

Please sign in to comment.