Skip to content

Commit ad96cf6

Browse files
committed
flood fill
1 parent 5f79798 commit ad96cf6

19 files changed

Lines changed: 277 additions & 114 deletions

FF4/Map.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ public class Map
2121
public byte this[int y, int x]
2222
{
2323
get => _map[y, x];
24-
set
25-
{
26-
_map[y, x] = value;
27-
CompressRow(y);
28-
}
24+
set => _map[y, x] = value;
2925
}
3026

3127
public int CompressedSize => _compressedRowLengths.Sum();
@@ -145,7 +141,7 @@ public void GetCompressedData(out byte[] data, out ushort[] pointers)
145141
}
146142
}
147143

148-
private void CompressRow(int y)
144+
public void CompressRow(int y)
149145
{
150146
_compressedRows[y] = new byte[Width + 1]; // extra 0xFF at the end
151147

FF4MapEdit.sln.DotSettings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
2121
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
2222
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
23+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Chocobo/@EntryIndexedValue">True</s:Boolean>
2324
<s:Boolean x:Key="/Default/UserDictionary/Words/=Overworld/@EntryIndexedValue">True</s:Boolean>
25+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Recompress/@EntryIndexedValue">True</s:Boolean>
2426
<s:Boolean x:Key="/Default/UserDictionary/Words/=SNES/@EntryIndexedValue">True</s:Boolean>
2527
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tileset/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

FF4MapEdit/App.config

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<?xml version="1.0" encoding="utf-8" ?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33
<startup>
4-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.1"/>
55
</startup>
6-
</configuration>
6+
</configuration>

FF4MapEdit/FF4MapEdit.csproj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
<AppDesignerFolder>Properties</AppDesignerFolder>
1010
<RootNamespace>FF4MapEdit</RootNamespace>
1111
<AssemblyName>FF4MapEdit</AssemblyName>
12-
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
12+
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
1313
<FileAlignment>512</FileAlignment>
1414
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
1515
<WarningLevel>4</WarningLevel>
1616
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
17+
<TargetFrameworkProfile />
1718
</PropertyGroup>
1819
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1920
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -106,6 +107,17 @@
106107
</ItemGroup>
107108
<ItemGroup>
108109
<Resource Include="Hovercraft.ico" />
110+
<Resource Include="icons\iconfinder_arrow-redo_59814.png" />
111+
<Resource Include="icons\iconfinder_arrow-undo_59818.png" />
112+
<Resource Include="icons\iconfinder_Brush_131835.png" />
113+
<Resource Include="icons\iconfinder_Brush_132013.png" />
114+
<Resource Include="icons\iconfinder_color-picker_79715.png" />
115+
<Resource Include="icons\iconfinder_fill_44683.png" />
116+
<Resource Include="icons\iconfinder_Grid_131737.png" />
117+
<Resource Include="icons\iconfinder_rectangle_4470.png" />
118+
<Resource Include="icons\iconfinder_rectangle_84039.png" />
119+
<Resource Include="icons\iconfinder_Save_1493294.png" />
120+
<Resource Include="icons\iconfinder_folder_open_59927.png" />
109121
<Content Include="icons\open.png">
110122
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
111123
</Content>

FF4MapEdit/MainWindow.xaml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,40 @@
88
mc:Ignorable="d"
99
Title="FF4MapEdit" Height="720" Width="1280">
1010
<Grid>
11-
<ToolBarTray Height="30" VerticalAlignment="Top" Margin="0,0,0,0">
12-
<ToolBar x:Name="toolBar" HorizontalAlignment="Left" Height="30" VerticalAlignment="Top">
11+
<Grid.CommandBindings>
12+
<CommandBinding Executed="Undo_Executed" Command="ApplicationCommands.Undo"/>
13+
<CommandBinding Executed="Redo_Executed" Command="ApplicationCommands.Redo"/>
14+
</Grid.CommandBindings>
15+
<ToolBarTray Height="40" VerticalAlignment="Top" Margin="0,0,0,0">
16+
<ToolBar x:Name="ToolBar" HorizontalAlignment="Left" VerticalAlignment="Top">
1317
<Button x:Name="OpenButton" ToolTip="Open" Click="OpenButton_Click">
14-
<Image Source="icons/open.png"/>
18+
<Image Width="32" Height="32" Source="icons/iconfinder_folder_open_59927.png"/>
1519
</Button>
1620
<Button x:Name="SaveButton" ToolTip="Save" Click="SaveButton_Click">
17-
<Image Source="icons/save.png"/>
21+
<Image Width="32" Height="32" Source="icons/iconfinder_Save_1493294.png"/>
22+
</Button>
23+
<ComboBox x:Name="MapComboBox" Width="300" Height="32" SelectedValuePath="Tag" SelectionChanged="MapComboBox_SelectionChanged" />
24+
</ToolBar>
25+
<ToolBar>
26+
<ToggleButton x:Name="GridButton" ToolTip="Toggle Grid Lines">
27+
<Image Width="32" Height="32" Source="icons/iconfinder_Grid_131737.png"></Image>
28+
</ToggleButton>
29+
<ToggleButton x:Name="FloodFillButton" ToolTip="Flood Fill">
30+
<Image Width="32" Height="32" Source="icons/iconfinder_fill_44683.png"></Image>
31+
</ToggleButton>
32+
<Button x:Name="UndoButton" ToolTip="Undo" Click="UndoButton_OnClick">
33+
<Image Width="32" Height="32" Source="icons/iconfinder_arrow-undo_59818.png"></Image>
34+
</Button>
35+
<Button x:Name="RedoButton" ToolTip="Redo" Click="RedoButton_OnClick">
36+
<Image Width="32" Height="32" Source="icons/iconfinder_arrow-redo_59814.png"></Image>
1837
</Button>
19-
<ComboBox x:Name="MapComboBox" Width="300" SelectedValuePath="Tag" SelectionChanged="MapComboBox_SelectionChanged" />
2038
</ToolBar>
2139
</ToolBarTray>
22-
<Image x:Name="Tileset" HorizontalAlignment="Left" Height="128" Margin="10,35,0,0" VerticalAlignment="Top" Width="256" MouseLeftButtonUp="Tileset_MouseLeftButtonUp" Stretch="None"/>
23-
<ScrollViewer x:Name="MapScrollViewer" HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Left" Margin="271,35,0,0" VerticalAlignment="Top" ScrollChanged="ScrollViewer_ScrollChanged">
24-
<Image x:Name="Map" HorizontalAlignment="Left" Height="4096" Margin="0,0,0,0" VerticalAlignment="Top" Width="4096" MouseLeftButtonUp="Map_MouseLeftButtonUp" MouseLeftButtonDown="Map_MouseLeftButtonDown" MouseMove="Map_MouseMove" Stretch="None"/>
40+
<Image x:Name="Tileset" HorizontalAlignment="Left" Height="128" Margin="10,43,0,0" VerticalAlignment="Top" Width="256" MouseLeftButtonUp="Tileset_MouseLeftButtonUp" Stretch="None"/>
41+
<ScrollViewer x:Name="MapScrollViewer" HorizontalScrollBarVisibility="Auto" HorizontalAlignment="Left" Margin="271,43,0,0" VerticalAlignment="Top" ScrollChanged="ScrollViewer_ScrollChanged">
42+
<Image x:Name="Map" HorizontalAlignment="Left" Height="4096" Margin="0,0,0,0" VerticalAlignment="Top" Width="4096" MouseLeftButtonUp="Map_MouseLeftButtonUp" MouseLeftButtonDown="Map_MouseLeftButtonDown" MouseRightButtonDown="Map_MouseRightButtonDown" MouseMove="Map_MouseMove" Stretch="None"/>
2543
</ScrollViewer>
26-
<Label x:Name="SpaceUsedLabel" Content="" HorizontalAlignment="Left" Margin="10,433,0,0" VerticalAlignment="Top"/>
27-
<Grid x:Name="TilePropertiesGrid" HorizontalAlignment="Left" Margin="10,168,0,0" VerticalAlignment="Top" Width="256" Height="260" Visibility="Hidden">
44+
<Grid x:Name="TilePropertiesGrid" HorizontalAlignment="Left" Margin="10,176,0,0" VerticalAlignment="Top" Width="256" Height="260" Visibility="Hidden">
2845
<CheckBox x:Name="WalkCheckBox" Content="Walk" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
2946
<CheckBox x:Name="ChocoboWalkCheckBox" Content="Chocobo Walk" HorizontalAlignment="Left" Margin="10,30,0,0" VerticalAlignment="Top"/>
3047
<CheckBox x:Name="BlackChocoboFlyCheckBox" Content="Black Chocobo Fly" HorizontalAlignment="Left" Margin="10,50,0,0" VerticalAlignment="Top"/>
@@ -38,5 +55,7 @@
3855
<CheckBox x:Name="EnemyEncountersCheckBox" Content="Enemy Encounters" HorizontalAlignment="Left" Margin="10,210,0,0" VerticalAlignment="Top"/>
3956
<CheckBox x:Name="TriggerCheckBox" Content="Trigger" HorizontalAlignment="Left" Margin="10,230,0,0" VerticalAlignment="Top"/>
4057
</Grid>
58+
<Label x:Name="SpaceUsedLabel" Content="" HorizontalAlignment="Left" Margin="10,441,0,0" VerticalAlignment="Top"/>
59+
<Label x:Name="CoordinatesLabel" Content="" HorizontalAlignment="Left" Margin="10,476,0,0" VerticalAlignment="Top"/>
4160
</Grid>
4261
</Window>

FF4MapEdit/MainWindow.xaml.cs

Lines changed: 161 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,23 @@ public partial class MainWindow : Window
2828
private Tileset _tileset;
2929
private Map _map;
3030

31-
private int _selectedTile = -1;
31+
private byte _selectedTile;
3232
private GeometryDrawing _selectedTileDrawing = new GeometryDrawing();
3333
private WriteableBitmap[] _rowBitmaps;
3434

3535
private bool _painting = false;
3636

37+
private struct PaintedTile
38+
{
39+
public int X, Y;
40+
public byte OldTile;
41+
public byte NewTile;
42+
}
43+
44+
private List<PaintedTile> _paintingTiles = new List<PaintedTile>();
45+
private readonly Stack<List<PaintedTile>> _undoStack = new Stack<List<PaintedTile>>();
46+
private readonly Stack<List<PaintedTile>> _redoStack = new Stack<List<PaintedTile>>();
47+
3748
private int SpaceUsed
3849
{
3950
set
@@ -220,7 +231,7 @@ private void Tileset_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
220231

221232
private void SelectTile(int x, int y)
222233
{
223-
_selectedTile = 16 * y + x;
234+
_selectedTile = (byte)(16 * y + x);
224235

225236
HighlightSelectedTile(x, y);
226237

@@ -264,44 +275,127 @@ private void CheckTilePropertyBoxes()
264275

265276
private void Map_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
266277
{
267-
if (_selectedTile == -1)
278+
GetClickedTile(sender, e, out int x, out int y);
279+
if (x < 0 || x >= _map.Width || y < 0 || y >= _map.Height)
268280
{
269281
return;
270282
}
271283

272-
GetClickedTile(sender, e, out int x, out int y);
273-
if (x < 0 || x >= _map.Width || y < 0 || y >= _map.Height)
284+
if (FloodFillButton.IsChecked == true)
274285
{
275-
return;
286+
FloodFill(x, y);
287+
}
288+
else
289+
{
290+
Paint(x, y);
291+
_painting = true;
276292
}
277-
278-
Paint(x, y);
279-
_painting = true;
280293
}
281294

282295
private void Map_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
283296
{
284297
_painting = false;
298+
299+
if (_paintingTiles.Count > 0)
300+
{
301+
RecompressMap(_paintingTiles);
302+
303+
_undoStack.Push(_paintingTiles);
304+
_redoStack.Clear();
305+
_paintingTiles = new List<PaintedTile>();
306+
}
307+
}
308+
309+
private void RecompressMap(List<PaintedTile> paintingTiles)
310+
{
311+
var rowsToCompress = paintingTiles.Select(tile => tile.Y).Distinct();
312+
foreach (var row in rowsToCompress)
313+
{
314+
_map.CompressRow(row);
315+
}
316+
317+
SpaceUsed = _map.CompressedSize;
318+
}
319+
320+
private void Map_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
321+
{
322+
GetClickedTile(sender, e, out int x, out int y);
323+
if (x < 0 || x >= _map.Width || y < 0 || y >= _map.Height || _map[y, x] == _selectedTile)
324+
{
325+
return;
326+
}
327+
328+
var selectedTile = _map[y, x];
329+
SelectTile(selectedTile % 16, selectedTile / 16);
285330
}
286331

287332
private void Map_MouseMove(object sender, MouseEventArgs e)
288333
{
289-
if (_painting)
334+
GetClickedTile(sender, e, out int x, out int y);
335+
CoordinatesLabel.Content = $"Coordinates: ({x:X2}, {y:X2})";
336+
337+
if (_painting && !(x < 0 || x >= _map.Width || y < 0 || y >= _map.Height) && _map[y, x] != _selectedTile)
290338
{
291-
GetClickedTile(sender, e, out int x, out int y);
292339
Paint(x, y);
293340
}
294341
}
295342

296343
private void Paint(int x, int y)
297344
{
298-
_map[y, x] = (byte)_selectedTile;
345+
_paintingTiles.Add(new PaintedTile
346+
{
347+
X = x,
348+
Y = y,
349+
OldTile = _map[y, x],
350+
NewTile = _selectedTile
351+
});
352+
353+
SetTile(x, y, _selectedTile);
354+
}
355+
356+
private void FloodFill(int x, int y)
357+
{
358+
var queue = new Queue<(int x, int y)>();
359+
var fillingTile = _map[y, x];
360+
if (fillingTile != _selectedTile)
361+
{
362+
Paint(x, y);
363+
queue.Enqueue((x, y));
364+
}
365+
366+
while (queue.Count > 0)
367+
{
368+
(x, y) = queue.Dequeue();
369+
if (x > 0 && _map[y, x - 1] == fillingTile)
370+
{
371+
Paint(x - 1, y);
372+
queue.Enqueue((x - 1, y));
373+
}
374+
if (x < _map.Width - 1 && _map[y, x + 1] == fillingTile)
375+
{
376+
Paint(x + 1, y);
377+
queue.Enqueue((x + 1, y));
378+
}
379+
if (y > 0 && _map[y - 1, x] == fillingTile)
380+
{
381+
Paint(x, y - 1);
382+
queue.Enqueue((x, y - 1));
383+
}
384+
if (y < _map.Height - 1 && _map[y + 1, x] == fillingTile)
385+
{
386+
Paint(x, y + 1);
387+
queue.Enqueue((x, y + 1));
388+
}
389+
}
390+
}
391+
392+
private void SetTile(int x, int y, byte tile)
393+
{
394+
_map[y, x] = tile;
299395

300396
_rowBitmaps[y].Lock();
301-
_rowBitmaps[y].WritePixels(new Int32Rect(16*x, 0, 16, 16), _tileset[_selectedTile], 16*2, 0);
397+
_rowBitmaps[y].WritePixels(new Int32Rect(16 * x, 0, 16, 16), _tileset[tile], 16 * 2, 0);
302398
_rowBitmaps[y].Unlock();
303-
304-
SpaceUsed = _map.CompressedSize;
305399
}
306400

307401
private void GetClickedTile(object sender, MouseButtonEventArgs e, out int x, out int y)
@@ -324,6 +418,58 @@ private static void PixelsToTile(Point position, out int x, out int y)
324418
y /= 16;
325419
}
326420

421+
private void Undo_Executed(object sender, ExecutedRoutedEventArgs args)
422+
{
423+
Undo();
424+
}
425+
426+
private void Redo_Executed(object sender, ExecutedRoutedEventArgs args)
427+
{
428+
Redo();
429+
}
430+
431+
private void UndoButton_OnClick(object sender, RoutedEventArgs e)
432+
{
433+
Undo();
434+
}
435+
436+
private void RedoButton_OnClick(object sender, RoutedEventArgs e)
437+
{
438+
Redo();
439+
}
440+
441+
private void Undo()
442+
{
443+
if (_undoStack.Count > 0)
444+
{
445+
var undo = _undoStack.Pop();
446+
foreach (var painting in undo)
447+
{
448+
SetTile(painting.X, painting.Y, painting.OldTile);
449+
}
450+
451+
_redoStack.Push(undo);
452+
453+
RecompressMap(undo);
454+
}
455+
}
456+
457+
private void Redo()
458+
{
459+
if (_redoStack.Count > 0)
460+
{
461+
var redo = _redoStack.Pop();
462+
foreach (var painting in redo)
463+
{
464+
SetTile(painting.X, painting.Y, painting.NewTile);
465+
}
466+
467+
_undoStack.Push(redo);
468+
469+
RecompressMap(redo);
470+
}
471+
}
472+
327473
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
328474
{
329475
MapScrollViewer.ScrollToHorizontalOffset(Math.Round(MapScrollViewer.HorizontalOffset));

0 commit comments

Comments
 (0)