实现类似于 InkCanvas 的描边绘制
Implementing stroke drawing similar to InkCanvas
我的问题实际上归结为准确的鼠标移动检测。
我需要创建自己的 InkCanvas 实现并且大部分都成功了,除了准确地绘制笔画。
void OnMouseMove(object sneder, MouseEventArgs e)
{
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
DrawBrush.Draw(intPosition, PixelDisplay);
UpdateStroke(intPosition); // calls CaptureMouse
}
这行得通。位图 (PixelDisplay) 已更新,一切正常。但是,任何类型的快速鼠标移动都会导致绘图中出现大量跳跃。我已将问题缩小到 e.GetPosition(this)
,这会阻止事件足够长的时间以致于不准确。
有一个 this 问题已经很久没有复兴了,它的答案不清楚或者根本没有明显的区别。
经过更多测试后,由于 e.GetPosition
.
,所述解决方案和类似想法失败了
看了源码我知道InkCanvas使用了类似的方法;检测设备,如果是鼠标,则获取其位置并捕获。我认为相同的过程没有理由不能在这里以相同的方式工作。
我最终能够部分解决这个问题。
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
if (DrawBrush == null)
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
// Calculate pixel coordinates based on the control height
var lastPoint = CurrentStroke?.Points.LastOrDefault(new IntVector(-1, -1));
// Uses System.Linq to grab the last stroke, if it exists
PixelDisplay.Lock();
// My special locking mechanism, effectively wraps Bitmap.Lock
if (lastPoint != new IntVector(-1, -1)) // Determine if we're in the middle of a stroke
{
var alphaAdd = 1d / new IntVector(intPosition.X - lastPoint.Value.X, intPosition.Y - lastPoint.Value.Y).Magnitude;
// For some interpolation, calculate 1 / distance (magnitude) of the two points.
// Magnitude formula: Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
var alpha = 0d;
var xDiff = intPosition.X - lastPoint.Value.X;
var yDiff = intPosition.Y - lastPoint.Value.Y;
while (alpha < 1d)
{
alpha += alphaAdd;
var adjusted = new IntVector(
Math2.FloorToInt((position.X + (xDiff * alpha)) / ratio.X),
Math2.FloorToInt((position.Y + (yDiff * alpha)) / ratio.Y));
// Inch our way towards the current intPosition
DrawBrush.Draw(adjusted, PixelDisplay); // Draw to the bitmap
UpdateStroke(intPosition);
}
}
DrawBrush.Draw(intPosition, PixelDisplay); // Draw the original point
UpdateStroke(intPosition);
PixelDisplay.Unlock();
此实现在最后一点和当前点之间进行插值以填补任何空白。例如,当使用非常小的画笔尺寸时,它并不完美,但仍然是一个解决方案。
一些评论
IntVector
是我懒惰实现的 Vector2,只是使用整数代替。
Math2
是帮手 class。 FloorToInt
是 (int)MathF.Round(...))
的缩写
我的问题实际上归结为准确的鼠标移动检测。
我需要创建自己的 InkCanvas 实现并且大部分都成功了,除了准确地绘制笔画。
void OnMouseMove(object sneder, MouseEventArgs e)
{
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
DrawBrush.Draw(intPosition, PixelDisplay);
UpdateStroke(intPosition); // calls CaptureMouse
}
这行得通。位图 (PixelDisplay) 已更新,一切正常。但是,任何类型的快速鼠标移动都会导致绘图中出现大量跳跃。我已将问题缩小到 e.GetPosition(this)
,这会阻止事件足够长的时间以致于不准确。
有一个 this 问题已经很久没有复兴了,它的答案不清楚或者根本没有明显的区别。
经过更多测试后,由于 e.GetPosition
.
看了源码我知道InkCanvas使用了类似的方法;检测设备,如果是鼠标,则获取其位置并捕获。我认为相同的过程没有理由不能在这里以相同的方式工作。
我最终能够部分解决这个问题。
var position = e.GetPosition(this);
if (!Rect.Contains(position))
return;
if (DrawBrush == null)
return;
var ratio = new Point(Width / PixelDisplay.Size.X, Height / PixelDisplay.Size.Y);
var intPosition = new IntVector(Math2.FloorToInt(position.X / ratio.X), Math2.FloorToInt(position.Y / ratio.Y));
// Calculate pixel coordinates based on the control height
var lastPoint = CurrentStroke?.Points.LastOrDefault(new IntVector(-1, -1));
// Uses System.Linq to grab the last stroke, if it exists
PixelDisplay.Lock();
// My special locking mechanism, effectively wraps Bitmap.Lock
if (lastPoint != new IntVector(-1, -1)) // Determine if we're in the middle of a stroke
{
var alphaAdd = 1d / new IntVector(intPosition.X - lastPoint.Value.X, intPosition.Y - lastPoint.Value.Y).Magnitude;
// For some interpolation, calculate 1 / distance (magnitude) of the two points.
// Magnitude formula: Math.Sqrt(Math.Pow(X, 2) + Math.Pow(Y, 2));
var alpha = 0d;
var xDiff = intPosition.X - lastPoint.Value.X;
var yDiff = intPosition.Y - lastPoint.Value.Y;
while (alpha < 1d)
{
alpha += alphaAdd;
var adjusted = new IntVector(
Math2.FloorToInt((position.X + (xDiff * alpha)) / ratio.X),
Math2.FloorToInt((position.Y + (yDiff * alpha)) / ratio.Y));
// Inch our way towards the current intPosition
DrawBrush.Draw(adjusted, PixelDisplay); // Draw to the bitmap
UpdateStroke(intPosition);
}
}
DrawBrush.Draw(intPosition, PixelDisplay); // Draw the original point
UpdateStroke(intPosition);
PixelDisplay.Unlock();
此实现在最后一点和当前点之间进行插值以填补任何空白。例如,当使用非常小的画笔尺寸时,它并不完美,但仍然是一个解决方案。
一些评论
IntVector
是我懒惰实现的 Vector2,只是使用整数代替。
Math2
是帮手 class。 FloorToInt
是 (int)MathF.Round(...))