Опубликован: 23.08.2014 | Уровень: для всех | Доступ: платный | ВУЗ: Северный (Арктический) федеральный университет им. М.В. Ломоносова
Лабораторная работа 3:

Разработка графического редактора с жестовым управлением

< Лекция 5 || Лабораторная работа 3 || Лекция 6 >
Ключевые слова: Windows, API, приложение

На данном практическом занятии мы познакомимся с основными принципами отрисовки простых геометрических объектов, научимся использовать функции рисования Windows API и создадим простое приложение, которое в зависимости от распознанного жеста производит отрисовку той или иной геометрической фигуры.

  1. Для начала необходимо создать новый проект. Поскольку мы будем использовать Windows API, выберите проект Win32.
    Выбор проекта Win32

    увеличить изображение
    Рис. 5.1. Выбор проекта Win32
    Далее необходимо выбрать пункт "Приложение Windows", чтобы VisualStudio скомпилировал готовый для работы проект.
    Определение типа приложения Win32

    увеличить изображение
    Рис. 5.2. Определение типа приложения Win32
    Подключение SDK к Win32 проекту осуществляется стандартным образом. Не забудьте изменить пункт "Библиотека времени выполнения" на Многопоточную отладку (/MTd), иначе попытка компиляции проекта с использованием SDK не увенчается успехом.
    Изменение библиотеки времени выполнения

    увеличить изображение
    Рис. 5.3. Изменение библиотеки времени выполнения
  2. Для облегчения распознавания жестов воспользуемся готовой утилитой gesture_render.cpp. Скопируйте ее из дополнительных материалов к лабораторной работе в папку своего проекта, а также каталог res. Далее необходимо подключить утилиту, каталог res и gesture_viewer.rc (находится внутри каталога res) к вашему проекту.

    Для этого в обозревателе решений нажмите правой кнопкой мыши на папке с файлами ресурсов, выберите пункт "Добавить" и "Добавить существующий элемент".

    Подключение утитлиты gesture_render.cpp и gesture_viewer.rc к проекту

    увеличить изображение
    Рис. 5.4. Подключение утитлиты gesture_render.cpp и gesture_viewer.rc к проекту
    Следующим шагом добавим каталог res к компилятору проекта. Для этого в свойствах проекта выберите свойства конфигурации, далее вкладку C/C++ и добавьте каталог res в дополнительные каталоги включаемых файлов, как на рисунке.
    Добавление каталога к компилятору проекта

    увеличить изображение
    Рис. 5.5. Добавление каталога к компилятору проекта
  3. Приступим непосредственно к написанию кода программы.

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

    В первую очередь необходимо определить в качестве глобальной переменной массив для хранения флагов, в зависимости от true/false значения которых будет или не будет осуществляться прорисовка фигур. Также необходимо определить константу, задающую размер этого массива, и еще одну переменную-флаг drawready, с помощью которой будет вызываться отрисовка окна приложения. Таким образом, определение констант и глобальных переменных будет выглядеть следующим образом:
    #define MAX_PARAMS 10 // максимальная длина массива флагов для рисования
    
    // Глобальные переменные:
    HINSTANCE hInst; // текущий экземпляр
    HWND hWnd; //!делаем основное окно программы глобальным
    bool paintFlag[MAX_PARAMS];  // массив флагов для рисования
    bool drawready = false; // флаг для начала отрисовки
          
    Далее опишем класс GesturePipeline, который будет наследовать от класса UtilPipeline, и определим связь между массивом флагов и распознаваемыми жестами, для чего переопределяем функцию OnGesture. При распознавании позы "победа" приложение должно останавливать распознавание жестов и выполнять рисование, поэтому меняем значение флага drawready.
    //Создание наследника класса UtilPipeline
    class GesturePipeline: public UtilPipeline {
    public:
    //создание конструктора
    GesturePipeline (void):UtilPipeline(),m_render(L"Gesture Viewer") {
    //вызов функции позволяющей распозновать жесты
    	EnableGesture(); 
    }
    //функция OnGesture вызывается, когда жест распознан 
    virtual void PXCAPI OnGesture(PXCGesture::Gesture *data) {
    	
    if (data->active) m_gdata = (*data);
    switch (data->label)
    	{
    	case PXCGesture::Gesture::LABEL_POSE_PEACE: drawready = true;  	break;//действие на позу победа
    	case PXCGesture::Gesture::LABEL_NAV_SWIPE_LEFT: paintFlag[0] = true;  break; //действие на жест скольжение влево
    	case PXCGesture::Gesture::LABEL_NAV_SWIPE_RIGHT: paintFlag[1] = true;  break; //дествие на жест скольжение вправо
    	
    	case PXCGesture::Gesture::LABEL_POSE_THUMB_DOWN: paintFlag[2] = true;  break; //действие на позу большой палец вверх 
    	case PXCGesture::Gesture::LABEL_POSE_THUMB_UP: paintFlag[3] = true;  break;//действие на позу большой палец вниз
    	case PXCGesture::Gesture::LABEL_HAND_WAVE: paintFlag[4] = true;  break;//действие на позу помахивание
    	}
    
    }
          
    Переопределим функцию OnNewFrame таким образом, чтобы, если флаг drawready имеет значение true, обнаружение новых фреймов прекращалось
    //обнаружение нового фрейма
    virtual bool OnNewFrame(void) {
    if (drawready) return false; //если была распознана поза победы - выход из цикла
    else return m_render.RenderFrame(QueryImage(PXCImage::IMAGE_TYPE_DEPTH),
    QueryGesture(), &m_gdata);
    
    }
    
    protected:
    GestureRender m_render;
    PXCGesture::Gesture m_gdata;
    };
          
    Далее необходимо осуществить изменения в методе _tWinMain. В первую очередь, необходимо инициализировать массив флагов.
    for (int i=0; i<MAX_PARAMS; i++) paintFlag[i] = false; // инициализация глобального массива флагов для рисования
          
    Следующий код создает GesturePipeline объект, который вызывает метод LoopFrames(), обрабатывающий все фреймы, отправляемые камерой.
    GesturePipeline pipeline;
    pipeline.LoopFrames();
          
    Необходимо внести небольшое изменение в блок инициализации приложения. Запуск окна будет происходить только в тот момент, когда флаг drawready истинен, то есть был распознан жест "победа".
    // Выполнить инициализацию приложения:
    if (drawready&&!InitInstance (hInstance, nCmdShow))//
    {
    	return FALSE;
    }
          
  4. Рассмотрим некоторые распространенные команды, использующиеся для рисования в Windows API.

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

    Отрисовка окна происходит при получении сообщения WM_PAINT. Код рисования нужно помещать в блок метода WndProc, описывающий обработку сообщения WM_PAINT. Для рисования необходимо определить объект класса HDC (handle to a device context), в нашем случае - это hdc, именно к этому объекту будут обращаться все функции отрисовки. Также для отрисовки требуется объект класса PAINTSTRUCT, это объект ps.

    Вызов метода BeginPaint подготавливает окно к дальнейшей отрисовке, а метод EndPaint закрывает процесс отрисовки, поэтому код рисования нужно обязательно располагать между вызовами этих двух методов.

    Чтобы закрасить пиксель с координатами х и у в определенный цвет (например, в красный), необходимо вызвать метод SetPixel(hdc, х, у, RGB(255, 0, 0)). Последний параметр в вызове SetPixel отвечает за цвет пикселя в формате RGB (в данном случае - красный). Следующий код рисует на экране красный квадрат размером 100х100 пикселей:
    for (int i=50; i<150; i++) 	for (int j=50; j<150; j++) 	SetPixel(hdc, i,j, RGB(255, 0, 0));
          
    Для того, чтобы нарисовать линию, цикл использовать не нужно, существует специальная функция LineTo (hdc, х, у), которая рисует линию от текущей активной точки до точки с координатами х и у. Чтобы изменить положение текущей точки, используется метод MoveToEx (hdc, х, у, NULL), где х и у - координаты перемещения. Следующий код рисует прямую линию из точки (0,0) в точку (600,600):
    MoveToEx (hdc, 0, 0, NULL); LineTo (hdc, 600, 600);
          
    Если требуется нарисовать многоугольник, функция LineTo не используется. Для этих целей существует метод Polyline (hdc, apt, n), где apt - массив точек для соединения, n - количество точек, которые необходимо соединить. С помощью следующего кода происходит отрисовка восьмиугольника. Обратите внимание, чтобы фигура соединилась, первая и последняя точки в массиве должны совпадать.
    POINT apt[9] = { 200, 200, 150, 250, 150, 350, 200, 400, 300, 400, 350, 350, 350, 250, 300, 200, 200, 200 };
    Polyline (hdc, apt, 9);
          
    Для рисования таких фигур, как прямоугольник и эллипс, предусмотрены специальные методы Rectangle (hdc, xLeft, yTop, xRight, yBottom) и Ellipse (hdc, xLeft, yTop, xRight, yBottom). Определение положения фигуры происходит по четырем точкам, как показано на рисунке 5.6.
    Рис. 5.6. Определение положения прямоугольника и эллипса

    Следующий код выполняет отрисовку прямоугольника и круга:
    Rectangle (hdc, 450, 10, 850, 610); 	
    Ellipse (hdc, 350, 10, 660, 310);
          
    Код, содержащийся в обработке сообщения WM_PAINT, полностью представлен ниже.
    hdc = BeginPaint(hWnd, &ps);
    	// TODO: добавьте любой код отрисовки...
    
    	if (paintFlag[0]) { //рисуем красный квадрат
    		for (int i=50; i<150; i++) 
    			for (int j=50; j<150; j++)
    				SetPixel(hdc, i, j, RGB(255, 0, 0));
    	}
    
    	if (paintFlag[1]) { //рисуем линию
    		MoveToEx (hdc, 0, 0, NULL) ; 
    		LineTo (hdc, 600, 600) ;
    			
    	}
    	if (paintFlag[2]) {//рисуем многоугольник
    		POINT apt[9] = { 200, 200, 150, 250, 150, 350, 200, 400, 300, 400, 350, 350, 350, 250, 300, 200, 200, 200 };
    		Polyline (hdc, apt, 9) ;
    	}
    	if (paintFlag[3]) { //рисуем прямоугольник
    
    		Rectangle (hdc, 450, 10, 850, 610) ;
    	}
    	if (paintFlag[4]) { //рисуем эллипс
    			
    		Ellipse (hdc, 350, 10, 660, 310) ;
    	}
    
    	EndPaint(hWnd, &ps);
          
  5. При запуске программы появится окно с глубинным изображением с камеры, как на рисунке 5.7.
    Запуск программы, отображение глубинного изображения с камеры

    Рис. 5.7. Запуск программы, отображение глубинного изображения с камеры
    В случае распознавания приложением какого-либо жеста, в окне будет отображаться его схематическое отображение.
    Процесс распознавания приложением управляющих жестов

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

    увеличить изображение
    Рис. 5.9. Результат работы программы: отображение геометрических фигур на основе распознанных жестов
    Ниже представлен весь код файла ppainter.cpp
    #include "stdafx.h"
    #include "ppainter.h"
    #include "util_render.h"
    #include "util_pipeline.h"
    #include <windows.h>
    #include <stdlib.h>
    #include <string.h>
    #include <tchar.h>
    #include "gesture_render.h"
    #include "pxcgesture.h"
    
    
    #define MAX_LOADSTRING 100
    #define MAX_PARAMS 10 // максимальная длина массива флагов для рисования
    
    // Глобальные переменные:
    HINSTANCE hInst; // текущий экземпляр
    HWND hWnd; //!делаем основное окно программы глобальным, так как оно одно
    bool paintFlag[MAX_PARAMS];  // массив флагов для рисования
    bool drawready = false; // флаг для начала отрисовки
    
    TCHAR szTitle[MAX_LOADSTRING];					// Текст строки заголовка
    TCHAR szWindowClass[MAX_LOADSTRING];			// имя класса главного окна
    
    // Отправить объявления функций, включенных в этот модуль кода:
    ATOM				MyRegisterClass(HINSTANCE hInstance);
    BOOL				InitInstance(HINSTANCE, int);
    LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
    INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);
    
    
    //Создание наследника класса UtilPipeline
    class GesturePipeline: public UtilPipeline {
    public:
    //создание конструктора
    GesturePipeline (void):UtilPipeline(),m_render(L"Gesture Viewer") {
    //вызов функции позволяющей распозновать жесты
    	EnableGesture(); 
    }
    //функция OnGesture вызывается, когда жест распознан 
    virtual void PXCAPI OnGesture(PXCGesture::Gesture *data) {
    	
    if (data->active) m_gdata = (*data);
    switch (data->label) {
    	case PXCGesture::Gesture::LABEL_POSE_PEACE: drawready = true;  	break;//действие на позу победа
    	case PXCGesture::Gesture::LABEL_NAV_SWIPE_LEFT: paintFlag[0] = true;  break; //действие на жест скольжение влево
    	case PXCGesture::Gesture::LABEL_NAV_SWIPE_RIGHT: paintFlag[1] = true;  break; //действие на жест скольжение вправо
    	
    	case PXCGesture::Gesture::LABEL_POSE_THUMB_DOWN: paintFlag[2] = true;  break; //действие на позу большой палец вверх 
    	case PXCGesture::Gesture::LABEL_POSE_THUMB_UP: paintFlag[3] = true;  break;//действие на позу большой палец вниз
    	case PXCGesture::Gesture::LABEL_HAND_WAVE: paintFlag[4] = true;  break;//действие на позу помахивание
    	}
    
    }
    
    //обнаружение нового фрейма
    virtual bool OnNewFrame(void) {
    if (drawready) return false; //если была распознана поза победы - выход из цикла
    else return m_render.RenderFrame(QueryImage(PXCImage::IMAGE_TYPE_DEPTH),
    QueryGesture(), &m_gdata);
    
    }
    
    protected:
    GestureRender m_render;
    PXCGesture::Gesture m_gdata;
    };
    
    int APIENTRY _tWinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPTSTR    lpCmdLine,
                         int       nCmdShow)
    {
    	UNREFERENCED_PARAMETER(hPrevInstance);
    	UNREFERENCED_PARAMETER(lpCmdLine);
    
    	for (int i=0; i<MAX_PARAMS; i++) paintFlag[i] = false; // инициализация глобального массива 
    	флагов для рисования
    	
     	// TODO: разместите код здесь.
    	MSG msg;
    	HACCEL hAccelTable;
    
    	// Инициализация глобальных строк
    	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    	LoadString(hInstance, IDC_PPAINTER, szWindowClass, MAX_LOADSTRING);
    	MyRegisterClass(hInstance);
    
    	GesturePipeline pipeline;
    	pipeline.LoopFrames();
    
    	// Выполнить инициализацию приложения:
    	if (drawready&&!InitInstance (hInstance, nCmdShow))//
    	{
    		return FALSE;
    	}
    
    	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PPAINTER))
    	
    	// Цикл основного сообщения:
    	while (GetMessage(&msg, NULL, 0, 0))
    	{
    		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    		{ 	TranslateMessage(&msg);
    			DispatchMessage(&msg);
    		}
    	}
    	return (int) msg.wParam;
    }
    
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
    	WNDCLASSEX wcex;
    
    	wcex.cbSize = sizeof(WNDCLASSEX);
    
    	wcex.style			= CS_HREDRAW | CS_VREDRAW;
    	wcex.lpfnWndProc	= WndProc;
    	wcex.cbClsExtra		= 0;
    	wcex.cbWndExtra		= 0;
    	wcex.hInstance		= hInstance;
    	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PPAINTER));
    	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
    	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
    	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_PPAINTER);
    	wcex.lpszClassName	= szWindowClass;
    	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
    	return RegisterClassEx(&wcex);
    }
    
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {   hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной переменной
        hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
    
       if (!hWnd)    {      return FALSE;   }
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);
       return TRUE;
    }
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	int wmId, wmEvent;
    	PAINTSTRUCT ps;
    	HDC hdc; 
    		
    	switch (message)
    	{
    
         break; 
    		case WM_COMMAND:
    		wmId    = LOWORD(wParam);
    		wmEvent = HIWORD(wParam);
    		// Разобрать выбор в меню:
    		switch (wmId)
    		{
    		case IDM_ABOUT:
    			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
    			break;
    		case IDM_EXIT:
    			DestroyWindow(hWnd);
    			break;
    		default:
    			return DefWindowProc(hWnd, message, wParam, lParam);
    		}
    		break;
    	case WM_PAINT:
    		hdc = BeginPaint(hWnd, &ps);
    		// TODO: добавьте любой код отрисовки...
    
    		if (paintFlag[0]) { //рисуем красный квадрат
    			for (int i=50; i<150; i++) 
    				for (int j=50; j<150; j++)
    					SetPixel(hdc, i, j, RGB(255, 0, 0));
    		}
    
    		if (paintFlag[1]) { //рисуем линию
    			MoveToEx (hdc, 0, 0, NULL) ; 
    			LineTo (hdc, 600, 600) ;
    			
    		}
    		if (paintFlag[2]) {//рисуем многоугольник
    			POINT apt[9] = { 200, 200, 150, 250, 150, 350, 200, 400, 300, 400, 
    			350, 350, 350, 250, 300, 200, 200, 200 };
    			Polyline (hdc, apt, 9) ;
    		}
    		if (paintFlag[3]) { //рисуем прямоугольник
    
    			Rectangle (hdc, 450, 10, 850, 610) ;
    		}
    		if (paintFlag[4]) { //рисуем эллипс
    			
    			Ellipse (hdc, 350, 10, 660, 310) ;
    		}
    
    		EndPaint(hWnd, &ps);
    		break;
    
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		break;
    	default:
    		return DefWindowProc(hWnd, message, wParam, lParam);
    	}
    	return 0;
    }
    
    // Обработчик сообщений для окна "О программе".
    INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
    	UNREFERENCED_PARAMETER(lParam);
    	switch (message)
    	{
    	case WM_INITDIALOG:
    		return (INT_PTR)TRUE;
    
    	case WM_COMMAND:
    		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
    		{
    			EndDialog(hDlg, LOWORD(wParam));
    			return (INT_PTR)TRUE;
    		}
    		break;
    	}
    	return (INT_PTR)FALSE;
    }
          
    Листинг .

Задания для самостоятельной работы

  1. Реализуйте рисование различных фигур и линий для жестов LABEL_POSE_BIG5, LABEL_NAV_SWIPE_UP, LABEL_NAV_SWIPE_DOWN, LABEL_HAND_ CIRCLE. Измените код приложения таким образом, чтобы жест помахивания LABEL_HAND_WAVE стирал все нарисованные фигуры.
  2. Попробуйте подключить к проекту любую популярную графическую библиотеку (например, OpenGL, cairo или Qt)и реализовать рисование геометрических примитивов с использованием функций этой библиотеки.
  3. Попробуйте реализовать рисование произвольных линий с помощью отслеживания положения указательного пальца. В данном случае с помощью жестов можно управлять цветом и толщиной линии.
< Лекция 5 || Лабораторная работа 3 || Лекция 6 >
Дмитрий Юнушкин
Дмитрий Юнушкин

В лабораторной работе №2 (идентификация лица) сказано:

в FaceTracking.cs: удалим или закомментируем функцию SimplePipelineкласс MyUtilMPipeline и изменим функцию AdvancedPipeline...

Класса MyUtilMPipeline  нет в проекте вообще;

Функции AdvancedPipeline так же нет. Материалов к лабораторной  №2 в начале работы (по ссылке открывается та же страница) тоже нет.Это ошибки или используется другая версия примера?

Анатолий Федоров
Анатолий Федоров
Россия, Москва, Московский государственный университет им. М. В. Ломоносова, 1989
Дмитрий Юнушкин
Дмитрий Юнушкин
Россия, г. Пенза