实现类似于 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(...))

的缩写