Created
April 22, 2023 17:57
-
-
Save tomeko/6b779d37e73e733aed50dcdb88733ddf to your computer and use it in GitHub Desktop.
Fuzzy select fill simple (Avalonia UI)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Avalonia.Controls; | |
using Avalonia.Media.Imaging; | |
using System.Collections.Generic; | |
using System; | |
using System.IO; | |
using System.ComponentModel; | |
using System.Runtime.CompilerServices; | |
using Avalonia; | |
using Avalonia.Media; | |
using Avalonia.Platform; | |
using System.Runtime.InteropServices; | |
using System.Linq; | |
namespace floodfillava | |
{ | |
public partial class MainWindow : Window, INotifyPropertyChanged | |
{ | |
const string TEST_IMAGE_PATH = @"t1.jpg"; | |
private WriteableBitmap _ImSrc; | |
public WriteableBitmap ImSrc { get { return _ImSrc; } set { _ImSrc = value; NotifyPropertyChanged(); } } | |
private double _Threshold = 50; | |
public double Threshold { get { return _Threshold; } set { _Threshold = value; NotifyPropertyChanged(); } } | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
DataContext = this; | |
ctlImSrc.PointerPressed += ImSrc_PointerPressed; | |
} | |
private void ImSrc_PointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) | |
{ | |
Point pt_click = e.GetPosition(ctlImSrc); | |
Point pt_im = new Point((ImSrc.Size.Width / ctlImSrc.Bounds.Width) * pt_click.X, (ImSrc.Size.Height / ctlImSrc.Bounds.Height) * pt_click.Y); | |
FloodFill(pt_im, (int)Threshold); | |
} | |
void FloodFill(Point start, int thresh) | |
{ | |
int im_w = (int)ImSrc.Size.Width; | |
int im_h = (int)ImSrc.Size.Height; | |
UInt32[,] imdat = new UInt32[im_h, im_w]; | |
var imp = ImSrc.Lock(); | |
unsafe | |
{ | |
fixed (UInt32* ptr = imdat) | |
{ | |
int sz = imp.RowBytes * im_h * 4; | |
CopyMemory((void*)imp.Address, ptr, imp.RowBytes * im_h); | |
} | |
} | |
Color targetColor = imdat.GetColor((int)start.X, (int)start.Y); | |
HashSet<Point> selectedPoints = new HashSet<Point>(); | |
Queue<Point> pointsToCheck = new Queue<Point>(); | |
pointsToCheck.Enqueue(start); | |
while (pointsToCheck.Count > 0) | |
{ | |
Point currentPoint = pointsToCheck.Dequeue(); | |
if (selectedPoints.Contains(currentPoint)) continue; | |
Color currentColor = imdat.GetColor((int)currentPoint.X, (int)currentPoint.Y); | |
int colorDifference = ColorDifference(targetColor, currentColor); | |
if (colorDifference <= thresh) | |
{ | |
selectedPoints.Add(currentPoint); | |
IEnumerable<Point> neighbors = GetNeighbors(currentPoint, im_w, im_h); | |
foreach (Point neighbor in neighbors) | |
{ | |
if (!selectedPoints.Contains(neighbor)) | |
{ | |
pointsToCheck.Enqueue(neighbor); | |
} | |
} | |
} | |
} | |
Color color = new Color(255, 255, 0, 0); | |
foreach (var pt in selectedPoints) | |
imp.SetPixel((int)pt.X, (int)pt.Y, color); | |
imp.Dispose(); | |
ctlImSrc.Source = null; | |
ctlImSrc.Source = ImSrc; | |
} | |
private static IEnumerable<Point> GetNeighbors(Point point, int width, int height) | |
{ | |
List<Point> neighbors = new List<Point>(); | |
var x = point.X; | |
var y = point.Y; | |
if (x > 0) neighbors.Add(new Point(x - 1, y)); | |
if (x < width - 1) neighbors.Add(new Point(x + 1, y)); | |
if (y > 0) neighbors.Add(new Point(x, y - 1)); | |
if (y < height - 1) neighbors.Add(new Point(x, y + 1)); | |
return neighbors; | |
} | |
private static int ColorDifference(Color color1, Color color2) | |
{ | |
int redDifference = Math.Abs(color1.R - color2.R); | |
int greenDifference = Math.Abs(color1.G - color2.G); | |
int blueDifference = Math.Abs(color1.B - color2.B); | |
return redDifference + greenDifference + blueDifference; | |
} | |
private async void Load_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) | |
{ | |
var ofd = new OpenFileDialog() { Title = "Load Image", AllowMultiple = false, Filters = new List<FileDialogFilter>() { new FileDialogFilter() { Extensions = new List<string>() { "jpg", "jpeg", "JPG" }, Name = "JPG" } } }; | |
var file = await ofd.ShowAsync(this); | |
if (file != null && File.Exists(file[0])) | |
{ | |
loadtestim(file[0]); | |
} | |
} | |
void loadtestim(string fpath) | |
{ | |
WriteableBitmap wb = null; | |
using (FileStream fs = new FileStream(TEST_IMAGE_PATH, FileMode.Open)) | |
{ | |
ImSrc = WriteableBitmap.Decode(fs); | |
} | |
ctlImSrc.Source = ImSrc; | |
} | |
public event PropertyChangedEventHandler PropertyChanged; | |
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") | |
{ | |
if (PropertyChanged != null) | |
{ | |
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); | |
} | |
} | |
public unsafe static void CopyMemory(void* srcPtr, void* dstPtr, int count) => new Span<byte>(srcPtr, count).CopyTo(new Span<byte>(dstPtr, count)); | |
} | |
public static class Extensions | |
{ | |
public static Color GetColor(this UInt32[,] dat, int x, int y) | |
{ | |
return Color.FromArgb((byte)(dat[y,x] >> 24), (byte)(dat[y, x] >> 16), (byte)(dat[y, x] >> 8), (byte)dat[y, x]); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window xmlns="https://github.com/avaloniaui" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | |
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | |
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" | |
x:Class="floodfillava.MainWindow" | |
Title="floodfillava"> | |
<Grid RowDefinitions="50,*" ColumnDefinitions="*,*"> | |
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="20,0,0,0"> | |
<Button Content="Load" Click="Load_Click" Margin="0,0,30,0"/> | |
<Slider Minimum="0" Maximum="255" Value="{Binding Threshold, Mode=TwoWay}" Width="300" Margin="0,0,10,0"/> | |
<TextBlock Text="{Binding Threshold, StringFormat={}{0:N0}}" VerticalAlignment="Center" Width="60"/> | |
<TextBlock Text="Threshold" VerticalAlignment="Center"/> | |
</StackPanel> | |
<Image x:Name="ctlImSrc" Stretch="Uniform" StretchDirection="DownOnly" Grid.Row="1" Grid.Column="0"/> | |
<Image x:Name="ctlImDst" Stretch="Uniform" StretchDirection="DownOnly" Grid.Row="1" Grid.Column="1"/> | |
</Grid> | |
</Window> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment