在MS Paint上编写绘画程序 - 如何在鼠标移动事件之间进行插值?

时间:2010-07-27 20:04:59

标签: user-interface wxwidgets mouseover interpolation paint

我想以MS Paint的风格编写一个绘画程序。

当用户移动鼠标时,为了在屏幕上绘画,我必须等待鼠标移动事件并在我收到鼠标时在屏幕上绘图。显然,mose移动事件不经常发送,所以我必须通过在当前鼠标位置和前一个鼠标位置之间画一条线来插值鼠标移动。在伪代码中,这看起来像这样:

var positionOld = null

def handleMouseMove(positionNew):
    if mouse.button.down:
        if positionOld == null:
            positionOld = positionNew
        screen.draw.line(positionOld,positionNew)
        positionOld = positionNew

现在我的问题:使用直线段进行插值看起来太符合我的口味了,你能推荐一种更好的插值方法吗? GIMP或Adobe Photoshop采用什么方法实现?

或者,有没有办法增加我收到的鼠标移动事件的频率?我正在使用的GUI框架是wxWidgets

GUI框架:wxWidgets。
(编程语言:Haskell,但这与此无关)

编辑:澄清:我想看的东西看起来比直线段更平滑,看图片(原始尺寸):

jagged lines drawn between mouse positions http://i26.tinypic.com/hwa42h.jpg

EDIT2:我正在使用的代码如下所示:

-- create bitmap and derive drawing context
im      <- imageCreateSized (sy 800 600)
bitmap  <- bitmapCreateFromImage im (-1)    -- wxBitmap
dc      <- memoryDCCreate                   -- wxMemoryDC
memoryDCSelectObject dc bitmap

...
-- handle mouse move
onMouse ... sw (MouseLeftDrag posNew _) = do
    ...
    line dc posOld posNew [color     := white
                          , penJoin  := JoinRound
                          , penWidth := 2]
    repaint sw                              -- a wxScrolledWindow

-- handle paint event
onPaint ... = do
    ...
    -- draw bitmap on the wxScrolledWindow
    drawBitmap dc_sw bitmap pointZero False []

可能会有所作为。也许我对wx-classes的选择就是为什么我的鼠标移动事件频率相当低。

6 个答案:

答案 0 :(得分:3)

现场演示

enter image description here

The way to go is 

点的样条插值

解决方案是存储点的坐标,然后执行样条插值。

我采用了here演示的解决方案并对其进行了修改。他们在您停止绘图后计算样条曲线。我修改了代码,以便立即绘制。您可能会看到样条曲线在绘图期间发生了变化。对于实际应用,您可能需要两个画布 - 一个带有旧图纸,另一个带有当前图纸,它会不断变化,直到鼠标停止。

版本1使用样条简化 - 删除接近线的点 - 这样可以使样条曲线更平滑,但产生的“稳定”结果更少。版本2使用了生产线上的所有点,并且产生了更稳定的解决方案(并且计算成本更低)。

答案 1 :(得分:2)

我认为您需要查看wxWidgets的Device Context文档。

我有一些代码如下:

//screenArea is a wxStaticBitmap
int startx, starty;
void OnMouseDown(wxMouseEvent& event)
{
    screenArea->CaptureMouse();
    xstart = event.GetX();
    ystart = event.GetY();
    event.Skip();
}
void OnMouseMove(wxMouseEvent& event)
{
    if(event.Dragging() && event.LeftIsDown())
    {
        wxClientDC dc(screenArea);
        dc.SetPen(*wxBLACK_PEN);
        dc.DrawLine(startx, starty, event.GetX(), event.GetY());
    }
    startx = event.GetX();
    starty = event.GetY();
    event.Skip();
}

我知道它是C ++,但你说语言无关紧要,所以我希望无论如何它都会有所帮助。

这让我这样做:

alt text

看起来比你的例子更顺畅。

答案 2 :(得分:2)

使用样条线可以使它们非常平滑: http://freespace.virgin.net/hugo.elias/graphics/x_bezier.htm

但是你必须延迟每个线段的绘制,直到一帧之后,这样你就有了起点和终点,以及可用于计算的下一个和前一个点。

答案 3 :(得分:2)

所以,正如我看到手绘曲线锯齿状边缘的问题,当鼠标移动得非常快时,是不是解决了!在我看来,需要解决系统中mousemove事件的轮询频率,即使用不同的鼠标驱动程序或smf ..第二种方式是数学..使用某种算法,准确地弯曲之间的直线当鼠标事件被轮询时两点。为了清晰的视图,你可以比较如何在photoshop中绘制自由手线以及如何在mspaint中...感谢大家..;)

答案 4 :(得分:1)

使用线段插入鼠标移动很好,GIMP也是这样做的,因为以下非常快速的鼠标移动屏幕截图显示:

GIMP uses line segments, too

因此,平滑度来自高频率的鼠标移动事件。 WxWidgets可以做到这一点,正如related question的示例代码所示。

问题在于您的代码,Heinrich。即,首先绘制一个大位图,然后将整个位图复制到屏幕上并不便宜!要估计您需要的效率,请将您的问题与视频游戏进行比较:每秒30个鼠标移动事件的平滑速率对应于30fps。复制双缓冲区对于现代机器来说没有问题,但WxHaskell可能没有针对视频游戏进行优化,因此您遇到一些抖动就不足为奇了。

解决方案是直接在屏幕上绘制尽可能多的内容,即只绘制线条,例如上面的链接所示。

答案 5 :(得分:1)

我同意哈维兹 - 问题没有解决。它应该通过在优先级线程中记录鼠标移动来解决操作系统级别,但我知道没有操作系统这样做。但是,应用程序开发人员也可以通过内插优于线性来解决此操作系统限制。

由于鼠标移动事件并不总是足够快,因此线性插值并不总是足够。

我对Rocketmagnet提出的样条想法进行了一些实验。

不要在两个点A和D之间放置一条直线,而是查看A之前的点P并使用具有以下控制点的三次样条曲线B = A + v'和C = D - w',其中

v = A - P,
w = D - A,
w' = w / 4 and
v' = v * |w| / |v| / 4.

这意味着我们以与线插值相同的角度落入第二个点,但是以前一个段进入的相同角度出一个起点,使边缘平滑。我们使用段的长度来控制两个控制点,以使弯曲的大小适合它的比例。

下图显示了数据点很少的结果(以灰色显示)。

enter image description here

序列从左上角开始,到中间结束。

如果使用前一个和下一个点同时调整两个角度,这里仍有一定程度的不安可以减轻,但这也意味着比一个角度少一个点。我发现这个结果已经令人满意,所以我没试过。