diff --git a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs index 3d2ec8b0..5d220e0d 100644 --- a/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs +++ b/src/HexManiac.Core/ViewModels/ImageEditorViewModel.cs @@ -29,6 +29,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { private bool withinInteraction, withinDropperInteraction, withinPanInteraction; private Point interactionStart; + private bool[,] selectedPixels; + #region ITabContent Properties private StubCommand close; @@ -81,7 +83,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels { public ICommand SelectTool => StubCommand(ref selectTool, arg => SelectedTool = arg); public event EventHandler RefreshSelection; - private void RaiseRefreshSelection() => RefreshSelection?.Invoke(this, EventArgs.Empty); + private void RaiseRefreshSelection(params Point[] toSelect) { + selectedPixels = new bool[PixelWidth, PixelHeight]; + foreach (var s in toSelect) selectedPixels[s.X, s.Y] = true; + RefreshSelection?.Invoke(this, EventArgs.Empty); + } private int xOffset, yOffset, width, height; public int XOffset { get => xOffset; private set => Set(ref xOffset, value); } @@ -116,6 +122,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { palettePointerAddress = palRun.PointerSources[0]; Palette = new PaletteCollection(this, model, history) { SourcePalette = palRun.Start }; Refresh(); + selectedPixels = new bool[PixelWidth, PixelHeight]; } // convenience methods @@ -205,11 +212,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } public bool ShowSelectionRect(Point point) { - if (withinInteraction && withinDropperInteraction) { - return eyeDropperStrategy.ShowSelectionRect(point); - } else { - return toolStrategy.ShowSelectionRect(point); - } + if (point.X < 0 || point.X >= PixelWidth || point.Y < 0 || point.Y >= PixelHeight) return false; + return selectedPixels[point.X, point.Y]; } public void Refresh() { @@ -285,6 +289,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { var processed = new HashSet(); while (toProcess.Count > 0) { var current = toProcess.Dequeue(); + if (processed.Contains(current)) continue; processed.Add(current); if (pixels[current.X, current.Y] != originalColorIndex) continue; @@ -301,6 +306,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } UpdateSpriteModel(); + NotifyPropertyChanged(nameof(PixelData)); } #region Nested Types @@ -309,7 +315,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels { void ToolHover(Point screenPosition); void ToolDrag(Point screenPosition); void ToolUp(Point screenPosition); - bool ShowSelectionRect(Point subPixelPosition); } private class DrawTool : IImageToolStrategy { @@ -331,6 +336,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { if (parent.WithinImage(point)) { var tile = parent.eyeDropperStrategy.Tile; if (tile == null) { + drawSize = 1; + drawPoint = point; parent.PixelData[parent.PixelIndex(point)] = element.Color; parent.pixels[point.X, point.Y] = element.Index; } else { @@ -347,7 +354,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { parent.NotifyPropertyChanged(nameof(PixelData)); } - parent.RaiseRefreshSelection(); + RaiseRefreshSelection(); } public void ToolHover(Point point) { @@ -366,7 +373,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels { drawSize = 0; } - parent.RaiseRefreshSelection(); + RaiseRefreshSelection(); } public void ToolUp(Point screenPosition) { @@ -384,6 +391,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels { return true; } + + private void RaiseRefreshSelection() { + var selectionPoints = new Point[drawSize * drawSize]; + for (int x = 0; x < drawSize; x++) for (int y = 0; y < drawSize; y++) selectionPoints[y * drawSize + x] = drawPoint + new Point(x, y); + parent.RaiseRefreshSelection(selectionPoints); + } } private class SelectionTool : IImageToolStrategy { @@ -431,6 +444,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels { selectionHeight = point.Y - selectionStart.Y; } } + + RaiseRefreshSelection(); } public void ToolHover(Point screenPosition) { } @@ -448,24 +463,6 @@ namespace HavenSoft.HexManiac.Core.ViewModels { } } - public bool ShowSelectionRect(Point point) { - var x = point.X / (int)parent.SpriteScale; - var y = point.Y / (int)parent.SpriteScale; - - var (start, width, height) = (selectionStart, selectionWidth, selectionHeight); - - if (parent.withinInteraction && underPixels == null) { - (start, width, height) = BuildRect(selectionStart, selectionWidth, selectionHeight); - } - - if (x < start.X) return false; - if (y < start.Y) return false; - if (x >= start.X + width) return false; - if (y >= start.Y + height) return false; - - return true; - } - public static (Point point, int width, int height) BuildRect(Point start, int dragX, int dragY) { if (dragX < 0) { start += new Point(dragX, 0); @@ -479,6 +476,18 @@ namespace HavenSoft.HexManiac.Core.ViewModels { return (start, dragX + 1, dragY + 1); } + private void RaiseRefreshSelection() { + var (start, width, height) = (selectionStart, selectionWidth, selectionHeight); + + if (parent.withinInteraction && underPixels == null) { + (start, width, height) = BuildRect(selectionStart, selectionWidth, selectionHeight); + } + + var selectionPoints = new Point[width * height]; + for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) selectionPoints[y * width + x] = start + new Point(x, y); + parent.RaiseRefreshSelection(selectionPoints); + } + private void SwapUnderPixelsWithCurrentPixels() { for (int x = 0; x < selectionWidth; x++) { for (int y = 0; y < selectionHeight; y++) { diff --git a/src/HexManiac.Tests/ImageEditorTests.cs b/src/HexManiac.Tests/ImageEditorTests.cs index 1fee8a8d..1f64692c 100644 --- a/src/HexManiac.Tests/ImageEditorTests.cs +++ b/src/HexManiac.Tests/ImageEditorTests.cs @@ -293,10 +293,11 @@ namespace HavenSoft.HexManiac.Tests { editor.Hover(0, 0); - Assert.True(editor.ShowSelectionRect(8, 8)); - Assert.True(editor.ShowSelectionRect(8, 9)); - Assert.True(editor.ShowSelectionRect(9, 8)); - Assert.True(editor.ShowSelectionRect(9, 9)); + Assert.True(editor.ShowSelectionRect(4, 4)); + Assert.False(editor.ShowSelectionRect(4, 5)); + Assert.False(editor.ShowSelectionRect(4, 3)); + Assert.False(editor.ShowSelectionRect(5, 4)); + Assert.False(editor.ShowSelectionRect(3, 4)); } [Fact] diff --git a/src/HexManiac.WPF/Controls/ImageEditorView.xaml b/src/HexManiac.WPF/Controls/ImageEditorView.xaml index afda3e92..24535dc4 100644 --- a/src/HexManiac.WPF/Controls/ImageEditorView.xaml +++ b/src/HexManiac.WPF/Controls/ImageEditorView.xaml @@ -42,11 +42,19 @@ - - - - - + + + + + + + + diff --git a/src/HexManiac.WPF/Controls/SelectionRender.cs b/src/HexManiac.WPF/Controls/SelectionRender.cs index c055790b..96510572 100644 --- a/src/HexManiac.WPF/Controls/SelectionRender.cs +++ b/src/HexManiac.WPF/Controls/SelectionRender.cs @@ -50,7 +50,7 @@ namespace HavenSoft.HexManiac.WPF.Controls { var format = PixelFormats.Indexed8; Width = desiredWidth; Height = desiredHeight; - FillSelection(pixels, desiredWidth, desiredHeight, stride); + FillSelection(pixels, stride); if (!(Source is WriteableBitmap wSource) || wSource.PixelWidth != desiredWidth || @@ -68,18 +68,38 @@ namespace HavenSoft.HexManiac.WPF.Controls { } private const byte FULL = 1; - private void FillSelection(byte[] pixels, int width, int height, int stride) { - for (int x = 0; x < width - 2; x++) { - for (int y = 0; y < height - 2; y++) { + private void FillSelection(byte[] pixels, int stride) { + var zoom = (int)ViewModel.SpriteScale; + void Line(int start, int next) { + for (int i = 0; i < zoom; i++) { + pixels[start] = FULL; + start += next; + } + } + + for (int x = 0; x < ViewModel.PixelWidth; x++) { + for (int y = 0; y < ViewModel.PixelHeight; y++) { if (!ViewModel.ShowSelectionRect(x, y)) continue; - if (!ViewModel.ShowSelectionRect(x - 1, y - 1)) pixels[x + 0 + stride * (y + 0)] = FULL; - if (!ViewModel.ShowSelectionRect(x - 1, y + 1)) pixels[x + 0 + stride * (y + 2)] = FULL; - if (!ViewModel.ShowSelectionRect(x + 1, y - 1)) pixels[x + 2 + stride * (y + 0)] = FULL; - if (!ViewModel.ShowSelectionRect(x + 1, y + 1)) pixels[x + 2 + stride * (y + 2)] = FULL; - if (!ViewModel.ShowSelectionRect(x - 1, y - 0)) pixels[x + 0 + stride * (y + 1)] = FULL; - if (!ViewModel.ShowSelectionRect(x - 0, y - 1)) pixels[x + 1 + stride * (y + 0)] = FULL; - if (!ViewModel.ShowSelectionRect(x + 1, y + 0)) pixels[x + 2 + stride * (y + 1)] = FULL; - if (!ViewModel.ShowSelectionRect(x + 0, y + 1)) pixels[x + 1 + stride * (y + 2)] = FULL; + + // each diagonal maps to a single pixel being placed + if (!ViewModel.ShowSelectionRect(x - 1, y - 1)) pixels[(y * stride + x) * zoom] = FULL; + if (!ViewModel.ShowSelectionRect(x - 1, y + 1)) pixels[((y + 1) * zoom + 1) * stride + x * zoom] = FULL; + if (!ViewModel.ShowSelectionRect(x + 1, y - 1)) pixels[(y * stride + x + 1) * zoom + 1] = FULL; + if (!ViewModel.ShowSelectionRect(x + 1, y + 1)) pixels[((y + 1) * zoom + 1) * stride + (x + 1) * zoom + 1] = FULL; + + // each edge maps to a line being placed + if (!ViewModel.ShowSelectionRect(x - 1, y)) { + Line((y * zoom + 1) * stride + x * zoom, stride); + } + if (!ViewModel.ShowSelectionRect(x + 1, y)) { + Line((y * zoom + 1) * stride + (x + 1) * zoom + 1, stride); + } + if (!ViewModel.ShowSelectionRect(x, y - 1)) { + Line(y * zoom * stride + x * zoom + 1, 1); + } + if (!ViewModel.ShowSelectionRect(x, y + 1)) { + Line(((y + 1) * zoom + 1) * stride + x * zoom + 1, 1); + } } } }