代码之家  ›  专栏  ›  技术社区  ›  Heinrich Apfelmus

编写绘图程序à 如何在鼠标移动事件之间进行插值?

  •  10
  • Heinrich Apfelmus  · 技术社区  · 16 年前

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

    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实现什么方法?

    wxWidgets .

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

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

    jagged lines drawn between mouse positions

    编辑2: 我使用的代码如下所示:

    -- 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类的选择是我鼠标移动事件频率很低的原因。

    6 回复  |  直到 6 年前
        1
  •  4
  •   Tomas    12 年前

    现场演示

    enter image description here

    The way to go is 
    

    我采取了解决办法 here 并修改了它。他们在你停止绘制后计算样条曲线。我修改了代码以便它立即绘制。您可能会看到样条曲线在绘制过程中发生了变化。对于实际应用程序,您可能需要两个画布-一个带有旧图形,另一个只带有当前图形,这将不断更改,直到鼠标停止。

        2
  •  2
  •   Rocketmagnet    15 年前

    http://freespace.virgin.net/hugo.elias/graphics/x_bezier.htm

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

        3
  •  2
  •   harviz    15 年前

    所以,正如我看到的问题锯齿边缘的写意曲线,当鼠标移动非常快,是没有解决的!!!在我看来,需要解决系统中mousemove事件的轮询频率问题,即使用不同的鼠标驱动程序或smf。。第二种方法是数学。。使用某种算法,在轮询出鼠标事件时,准确地弯曲两点之间的直线。。为了获得清晰的视图,您可以比较photoshop中的徒手画线和mspaint中的徒手画线。。谢谢各位……)

        4
  •  1
  •   Community Mohan Dere    9 年前

    我想你需要调查一下 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

    这似乎比你的例子要流畅得多。

        5
  •  1
  •   Community Mohan Dere    9 年前

    GIMP uses line segments, too

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

    问题出在你的代码里,海因里希。也就是说,先画一个大位图,然后复制 到屏幕上可不便宜!为了估计你需要多大的效率,把你的问题比作电子游戏:每秒30个鼠标移动事件的平滑速率相当于每秒30帧。复制一个双缓冲区对于现代机器来说是没有问题的,但是WxHaskell很可能没有针对视频游戏进行过优化,所以出现一些抖动也就不足为奇了。

    解决方案是只画尽可能多的必要的,即只是线,直接在屏幕上,例如如上面的链接所示。

        6
  •  1
  •   John    13 年前

    我用Rocketmagnet提出的样条线思想做了一点实验。

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

    这意味着我们落入第二个点,与直线插值的角度相同,但以与上一段相同的角度从一个起点开始,使边缘平滑。我们使用两个控制点距离的线段长度使折弯的大小与其比例相匹配。

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

    enter image description here

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

    这里仍然存在某种程度的不安,如果同时使用上一个点和下一个点来调整两个角度,这种不安可能会得到缓解,但这也意味着要比所得到的少画一个点。我发现这个结果已经令人满意了,所以我没有试。