Написание программы рисования à la MS 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.

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

Ответ 1

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

GIMP uses line segments, too

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

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

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

Ответ 2

Я думаю, вам нужно заглянуть в документацию 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();
}

Я знаю это на С++, но вы сказали, что язык не имеет значения, поэтому я надеюсь, что это все равно поможет.

Это позволяет мне сделать это:

alt text

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

enter image description here

The way to go is 

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

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

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

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

Ответ 6

Я согласен с 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

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

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