Написание программы рисования в стиле MS Paint - как интерполировать события перемещения мыши? - PullRequest
11 голосов
/ 28 июля 2010

Я хочу написать программу для рисования в стиле MS Paint.

Для рисования объектов на экране, когда пользователь перемещает мышь, мне приходится ждать событий перемещения мыши и рисовать на экране всякий раз, когда я их получаю. По-видимому, события перемещения Моше отправляются не очень часто, поэтому я должен интерполировать движение мыши, рисуя линию между текущей позицией мыши и предыдущей. В псевдокоде это выглядит примерно так:

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 .

Фреймворк с графическим интерфейсом: wxWidgets.
(Язык программирования: Haskell, но здесь это неважно)

РЕДАКТИРОВАТЬ: Уточнение: я хочу что-то, что выглядит плавнее, чем отрезки прямых линий, см. Изображение (оригинальный размер):

зубчатые линии, проведенные между позициями мыши 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-классов - это то, почему я получаю довольно низкую частоту событий перемещения мыши.

Ответы [ 6 ]

3 голосов
/ 20 января 2014

Живые демонстрации

enter image description here

The way to go is 

Сплайн-интерполяция точек

Решение состоит в том, чтобы сохранить координаты точек и затем выполнить сплайн-интерполяцию.

Я взял решение, показанное здесь , и изменил его.Они вычислили сплайн после того, как вы прекратили рисовать.Я изменил код так, чтобы он рисовал сразу.Вы можете заметить, что сплайн меняется во время рисования.Для реального применения вам, вероятно, понадобятся два полотна - одно со старыми рисунками, а другое только с текущим рисунком, которое будет постоянно меняться, пока ваша мышь не остановится.

В версии 1 используется сплайн-упрощение - удаляются точки, которыеблизко к линии - что приводит к более плавным сплайнам, но дает менее «стабильный» результат.Версия 2 использует все точки на линии и создает гораздо более стабильное решение (и в вычислительном отношении дешевле).

2 голосов
/ 06 апреля 2011

так что, как я вижу, проблема зазубренного края кривой, сделанной от руки, когда мышь перемещается очень быстро, не решена !!!По моему мнению, есть необходимость обойти частоту опроса события mousemove в системе, то есть, используя другой драйвер мыши или SMF. И второй способ - это математика. Используя некоторый алгоритм, чтобы точно изогнуть прямую линию междудве точки, когда событие мыши опрашивается .. Для наглядности вы можете сравнить, как нарисована линия свободной руки в фотошопе и как в mspaint .. спасибо, люди ..;)

2 голосов
/ 05 августа 2010

Вы можете сделать их действительно плавными, используя сплайны: http://freespace.virgin.net/hugo.elias/graphics/x_bezier.htm

Но вам придется отложить рисование каждого отрезка линии до одного кадра позже, чтобы у вас были начальная и конечная точки, плюсследующие и предыдущие точки, доступные для расчета.

2 голосов
/ 28 июля 2010

Я думаю, вам нужно заглянуть в документацию Device Context для wxWidgets.

У меня есть код, который рисует так:

//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

, что кажется значительно более плавным, чем ваш пример.

1 голос
/ 12 мая 2013

Я согласен с Harviz - проблема не решена.Это должно быть решено на уровне операционной системы путем записи движений мыши в приоритетном потоке, но ни одна из известных мне операционных систем этого не делает.Однако разработчик приложения также может обойти это ограничение операционной системы, интерполируя лучше, чем линейный.

Поскольку события перемещения мыши не всегда происходят достаточно быстро, линейной интерполяции не всегда достаточно.

Я немного поэкспериментировал с идеей сплайна, выдвинутой Rocketmagnet.

Вместо того, чтобы провести линию между двумя точками A и D, посмотрите на точку P, предшествующую A, и используйте кубический сплайн со следующими контрольными точками.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

Последовательность начинается слева вверху и заканчивается посередине.

Здесь все еще есть некоторый уровень беспокойства, который можно ослабить, если использовать как предыдущий, так иСледующая точка, чтобы отрегулировать для обоих углов, но это также означало бы нарисовать одну точку меньше, чем у того, кто получил.Я нахожу этот результат уже удовлетворительным, поэтому я не пытался.

1 голос
/ 19 августа 2010

Интерполяция движений мыши с отрезками хорошо, GIMP тоже так делает, как показано на следующем скриншоте из очень быстрого движения мыши:

GIMP uses line segments, too

Итак, плавностьпроисходит от высокой частоты событий перемещения мыши.WxWidgets может сделать это, как показывает пример кода для вопроса .

Проблема в вашем коде, Генрих.А именно, рисование сначала в большом растровом изображении, а затем копирование всего растрового изображения на экран не дешево!Чтобы оценить, насколько эффективно вы должны быть, сравните вашу проблему с видеоиграми: плавная скорость 30 событий перемещения мыши в секунду соответствует 30 кадрам в секунду.Копирование двойного буфера не является проблемой для современных машин, но WxHaskell, вероятно, не оптимизирован для видеоигр, поэтому неудивительно, что вы испытываете некоторое дрожание.линии, прямо на экране, например, как показано в ссылке выше.

...