Как подружить QML с чужим OpenGL контекстом. Часть III: Обработка пользовательского ввода

В данной статье я попытаюсь рассказать о том как передавать события мыши и клавиатуры в QQuickWindow, в случае его использования в связке с QQuickRenderControl. Причиной того, что этому необходимо уделять специальное внимание, является то, что в случае использования QQuickRenderControl, никакого окна на самом деле не создается, соответственно у QQuickWindow нет абсолютно никакой возможности получать какие либо события, и их приходится эмулировать. То же самое касается изменения размеров — об этой операции так же необходимо оповещать в явном виде.

Для тех кто пропустил предыдущие части:

Как инициируютя события в Qt

Отправка события в Qt осуществляется с помощью метода

bool QCoreApplication::sendEvent(QObject* receiver, QEvent* event) 

где,

  • receiver — получатель сообщения, в нашем случае это экземпляр QQuickWindow (или его потомка);
  • event — это экземпляр конкретного типа события;

Передача событий мыши

Для корректного функционирования достаточно реализовать 3 события мыши:

QEvent::MouseButtonPress:

    QPointF mousePoint( 150, 201 );     Qt::MouseButton button = Qt::LeftButton;     Qt::MouseButton buttons = Qt::LeftButton | Qt::RightButton;     Qt::KeyboardModifiers modifiers = Qt::AltModifier;      QMouseEvent mouseEvent( QEvent::MouseButtonPress, mousePoint, mousePoint, button, buttons, modifiers );     QCoreApplication::sendEvent( quickWindow, &mouseEvent ); 

где,

  • mousePoint — текущая позиция мыши в координатах QQuickWindow;
  • button — кнопка мыши вызвавшая данное событие;
  • buttons — все кнопки мыши нажатые в момент генерации события;
  • modifiers — нажатые на клавиатуре клавиши-модификаторы (Ctrl, Alt, Shift и т.д.);

mousePoint используется дважды, поскольку в первый раз передается в координатах QQuckWindow, второй раз в screen координатах. Но поскольку окно на самом не создается, трактуется всегда как окно верхнего уровня, и его позицией мы управляем самостоятельно, то передаем одно и то же значение (как будто окно находится всегда в верхнем левом углу экрана), а при установке позиции окна, просто будем этот факт учитывать.

QEvent::MouseMove:

    QPointF mousePoint( 170, 198 );     Qt::MouseButton button = Qt::NoButton;     Qt::MouseButton buttons = Qt::LeftButton | Qt::RightButton;     Qt::KeyboardModifiers modifiers = Qt::AltModifier;      QMouseEvent mouseEvent( QEvent::MouseMove, mousePoint, mousePoint, button, buttons, modifiers );     QCoreApplication::sendEvent( quickWindow, &mouseEvent ); 

Поскольку причиной события передвижения мыши является сам факт передвижения мыши, а не какая-либо из кнопок, переменной button присваивается значение Qt::NoButton.

QEvent::MouseButtonRelease:

    QPointF mousePoint( 160, 251 );     Qt::MouseButton button = Qt::LeftButton;     Qt::MouseButton buttons = Qt::LeftButton;     Qt::KeyboardModifiers modifiers = Qt::AltModifier;      QMouseEvent mouseEvent( QEvent::MouseButtonRelease, mousePoint, mousePoint, button, buttons, modifiers );     QCoreApplication::sendEvent( quickWindow, &mouseEvent ); 

button в данном случае означает кнопку являвшуюся причиной данного события, но в данном случае кнопка была отпущена, соответственно buttons она присутствовать уже не может (иначе Qt начинает обрабатывать это событие неверно).

Передача событий клавиатуры

Аналогично, для корректной обработки событий клавиатуры, достаточно реализовать 2 события:

QEvent::KeyPress:

    Qt::Key qtKey = Qt::Key_Space;     QKeyEvent keyEvent( QEvent::KeyPress, qtKey, Qt::NoModifier );     QCoreApplication::sendEvent( quickWindow, &keyEvent ); 

QEvent::KeyRelease:

    Qt::Key qtKey = Qt::Key_Space;     QKeyEvent keyEvent( QEvent::KeyRelease, qtKey, Qt::NoModifier );     QCoreApplication::sendEvent( quickWindow, &keyEvent ); 

Изменение размеров

Как уже упомяналось выше, offscreen окна в Qt трактутся как окна верхнего уровня, поэтому используем соответствующий метод:

    QSize newSize( 320, 240 );     quickWindow->setGeometry( 0, 0, newSize.width(), newSize.height() ); 

Помимо изменения размеров собственно окна, желательно так же изменить размеры FBO, т.к. в противном случае либо получим пикселизацию (при увеличении размера), либо бессмысленное расходование ресурсов (т.к. размер FBO будет больше чем требуется):

    if( context->makeCurrent( offscreenSurface ) ) {         destroyFbo();         createFbo();         context->doneCurrent();     } 

Поскольку изменить размер FBO невозможно, просто удаляем текущий и создаем новый ( см. детали в Первой части ).

На этом все.

Примеры реализации, как обычно, доступены на GitHub
Ну и как и прежде, коментарии, вопросы, здоровая критика — приветствуются.

Продолжение следует…

ссылка на оригинал статьи http://habrahabr.ru/post/249383/

Как мы делали Multitouch Table

Привет Хабр.
Занимаясь Computer Vision, я интересовался Natural интерфейсами, общался с людьми кто проектирует столы для баров с touch интерфейсами. И у меня появилась идея сделать свой. Дешево, сердито, но главное, чтобы все работало. То есть важно испытать и протестировать. А тут, мой друг Александр Жеделев, музыкальный продюссер Русского Драматического театра Эстонии, предложил сделать какой нибудь новый музыкальный инструмент для выступления на музыкальном фестивале Tallinn Music Week. Времени было немного, и мы приступили.

Вообще есть несколько подходов к созданию такого типа столов. Я попытаюсь описать три из них.
Общий принцип построения таких систем таков. Изображение, которое выдает проектор, отображается на стекле позади которого стоит покрытие, в качестве которого можно взять хоть бумагу для выпекания. Касание отражает любую часть излучения обратно, можно поставить под стеклом камеру и детектировать прикосновения. На рисунке внизу можно также увидеть, что помимо проектора, рука снизу подсвечивается источниками инфракрасного излучения (это один из способов построения таких систем).
image
Поскольку нам необходимо ловить только жесты прикосновений, а не картинку с проектора, то необходим способ, чтобы одно не влияло на другое. Для этого используются камеры настроенные на прием только инфракрасного изображения. Обычный сенсор матрицы камеры реагирует как на видимую, так и на инфракрасную часть спектра. На нижеприведенном видео можно увидеть, как камера телефона Sony Xperia реагирует на инфракрасный луч дистанционного датчика Sharp. Глаз же этого луча не видит, и для нас датчик в рабочем режиме воспринимается темным.

Обычно в камере стоит фильтр, который отсекает инфракрасный и пропускает только видимый спектр. Для наших целей нам необходимо переделать все наоборот. То есть снять обьектив, убрать оттуда фильтр, который пропускает только видимый свет, и поставить фильтр, который пропускает только инфракрасное излучение, поскольку жесты мы будет опознавать пользуясь этой частью спектра. Вообще подойдет любая веб-камера. Я взял старую добрую PS3 Eye, поскольку она дает оптимальный результат по цене/качеству. Само снятие фильтра также не представляет сложности. Например так.
Для того, чтобы отсечь видимый свет, необходимо собрать камеру и поставить перед обьективом фильтр, который пропускает только инфракрасное излучение. Его можно купить в радиомагазине. Он выглядит как темное красное стекло. Теперь у нас есть камера которая воспринимает IR излучение. Протестировать ее можно сразу, изображение с проектора должно выглядеть как серый однообразный фон.
Теперь о самих подходах.

Первый — дорогой, но достаточно надежный, это провести по бокам рамы стола, сразу же под стекло, цепь инфракрасных светодиодов, которые будут подсвечивать руки. Плюсы — очень четкое изображение отражения пальцев, которое достигается за счет близости излучающих светодиодов. Минусы — дополнительные сложности в установке диодов и общей схемы их питания. Как правило данная схема используется в коммерческих подходах.
image

Второй — это поставить дополнительные источники инфракрасного излучения под стекло рядом с проектором, как на рисунке в самом начале. Я пробовал этот подход, но выявил несколько недостатков. Излучение должно быть достаточно рассеяным, иначе какие то части будут освещаться сильнее остальных. И существует проблема бликов. Я испытывал пучок IR светодиодов, но они давали очень сильное направленное излучение, которое бликовало. Никакие рассеивающие фильтры не помогали, стекло частично отражало поток, и камера фиксировала постоянно присутствующее пятно. В общем это неудачный подход, и я бы не рекомендовал его. Равномерное освещение получить трудно.

Третий, который использовал я, самый простой и достаточно эффективный. На самом деле, нам и не нужна отдельная подсветка. Дело в том, что лампа проектора испускает излучение достаточно широкого спектра, в том числе и инфракрасное. Достаточно посмотреть через нашу модифицированную камеру на проекцию, и мы увидим равномерный серый фон. Бинго.

Сама рама была собрана из дерева. На нее посажено оргстекло. Камера находилась непосредственно под стеклом.
Это план лаборатории AudioKinetica. Внизу можно видеть схему стола, начерченную маркером.
image

А вот сам стол.

Для распознавания BLOB (это отражения-пятна, которые видит камера) я использовал систему Community Core Vision, CCV. Это готовое решение, которое ретранслирует распознавание и передает результаты по TUIO протоколу. TUIO это открытый фреймворк, который определяет протокол и API для построения multitouch интерфейсов. В принципе, если бы было больше времени, я бы написал свой детектор BLOB на OpenCV, поскольку ничего особо сложного для этой задачи нет. Пятна хорошо видятся, и на OpenCV это выглядело бы так — получаем картинку, убираем шум, строим битмап карту, пропускаем через Canny алгоритм, находим контура и далее транслируем их координаты в TUIO обьекты по спецификации. Понадобился бы модуль калибровки для задания координат. CCV использует вычитание картинки фона из полученного изображения, там есть также адаптивный режим, который учитывает медленные изменения фона. На OpenCV это можно реализовать методом codebook и connected components.
Теперь у нас есть система транслирующая TUIO обьекты, и мы можем использовать все, что принимает эти обьекты, или написать самим клиента. К примеру на Java это делается достаточно легко, в сети есть множество примеров.

Настройки CCV.
image

Далее, поскольку планировалось использовать данный стол для управления синтезом звука, то был использован модуль TUIO для Ableton, который позволяет привязать жесты к параметрам генерации звука, инструментам и так далее. После этого Александр Жеделев занимался настройкой тональности, сопряжения с другими записями, и в общем экспериментировал как хотел. В конце видео показано примерно как и что.

А вот уже подредактированная версия. Смотрите в наушниках.

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

Одно наблюдение. Не обязательно касаться стекла- Уже на некотором расстоянии от него происходит отражение, и камера фиксирует его. Там есть пространство для игры с настройками, и как результат можно делать воздушный тач-скрин.
Есть одно неудобство связанное с TUIO протоколом, его обьекты надо ретранслировать. То есть если я хочу паралелльно запустить красивые визуалы и синтез звука, то мне нужен ретранслятор, поскольку если модуль сопряжения принимает TUIO обьект, то допустим Flash визуалы этот обьект уже не видят.

Хочу сказать спасибо всем людям, кто участвовал, особенно Александру Жеделеву, Сергею Драгунову, Кристе Кёстер.
AudioKinetica

ссылка на оригинал статьи http://habrahabr.ru/post/249399/