Improve performance of ShowSelectionRect

This commit is contained in:
Benjamin Popp 2020-09-17 08:01:22 -05:00
parent 052434250e
commit 6279ab41c3
4 changed files with 86 additions and 48 deletions

View File

@ -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<ImageEditorTools>(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<Point>();
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++) {

View File

@ -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]

View File

@ -42,11 +42,19 @@
</TransformGroup>
</local:PixelImage.RenderTransform>
</local:PixelImage>
<local:SelectionRender Stretch="None" IsHitTestVisible="False" VerticalAlignment="Center" HorizontalAlignment="Center">
<local:SelectionRender.RenderTransform>
<TranslateTransform X="{Binding XOffset}" Y="{Binding YOffset}"/>
</local:SelectionRender.RenderTransform>
</local:SelectionRender>
<!-- Wrapping the selection render in a canvas to prevent clipping -->
<Canvas
IsHitTestVisible="False"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="{Binding ActualWidth, ElementName=SelectionRender}"
Height="{Binding ActualHeight, ElementName=SelectionRender}">
<local:SelectionRender x:Name="SelectionRender" Stretch="None">
<local:SelectionRender.RenderTransform>
<TranslateTransform X="{Binding XOffset}" Y="{Binding YOffset}"/>
</local:SelectionRender.RenderTransform>
</local:SelectionRender>
</Canvas>
</Grid>
</DockPanel>
</UserControl>

View File

@ -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);
}
}
}
}