лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdfАнимация |
541 |
|
|
Листинг 12.1. (продолжение) |
|
GetClientRect(hWnd, |
&rect); |
dX = rect.right / |
100.; |
dY = rect.bottom / 50.;
// Создать таймер (0.1 с) SetTimer(hWnd, 1, 100, NULL);
hBmpBkgr = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE),
MAKEINTRESOURCE(IDB_STONE));
hBkBrush = CreatePatternBrush(hBmpBkgr);
SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBkBrush);
hBmpBall = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), MAKEINTRESOURCE(IDB_BALL));
GetObject(hBmpBall, sizeof(bm), (LPSTR)&bm);
SetGraphicsMode(hDC, GM_ADVANCED); break;
case WM_TIMER: GetClientRect(hWnd, &rect);
// Стираем прежнюю картинку мяча
SetRect(&rBall, (int)x, (int)y, (int)x + bm.bmWidth, (int)y + bm.bmHeight);
FillRect(hDC, &rBall, hBkBrush);
//Новая позиция мяча x += dX;
y += dY; alpha += 10;
if (alpha > 360) alpha = 0;
// Если мяч достиг края окна, направление его движения изменяется if(x + bm.bmWidth > rect.right || x < 0)
dX = -dX;
if(y + bm.bmHeight > rect.bottom || y < 0) dY = -dY;
DrawBall(hWnd, hDC, hBmpBall, bm, x, y, alpha); break;
case WM_DESTROY: KillTimer(hWnd, 1); ReleaseDC(hWnd, hDC); PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//====================================================================
void DrawBall(HWND hwnd, HDC hdc, HBITMAP hBmp, BITMAP bm, FLOAT x, FLOAT y, int alpha) {
XFORM xform;
Анимация |
543 |
|
|
Stone.bmp с идентификатором IDB_STONE, а файл Ball.bmp — с идентификатором IDB_BALL. В результате в составе вашего проекта должны появиться файлы
BounceBall.rc и resource.h.
Когда вы успешно решите все эти проблемы и откомпилируете проект, в окне программы BounceBall появится летающий мячик, как показано на рис. 12.1.
Рис. 12.1. Прыгающий мяч в приложении BounceBall
Все работает замечательно, за исключением одной детали. Дело в том, что вра щающийся мячик отвратительно мерцает. Причиной этого является быстрое пос ледовательное выполнение двух операций с контекстом дисплея: стирание пре жнего изображения мяча (FillRect) и вывод нового изображения (BitBlt). Вы можете убедиться в этом, закомментировав вызов функции FillRect. После этого изображение перестанет мерцать, но вместо летающего мячика получится что то вроде червя, прогрызающего тоннель в камне.
Для решения обнаруженной нами проблемы обычно используется прием, из вестный как двойная буферизация.
Двойная буферизация
Неприятное мерцание изображения в анимационном приложении можно устра нить, если сформировать очередную фазу картинки в виртуальном контексте ус тройства. Для этого используется контекст в памяти, совместимый с контекстом дисплея. В нашем случае очередная фаза содержит две операции: а) стереть пред шествующее изображение мяча; б) нарисовать новое изображение мяча. После этого содержимое совместимого контекста копируется в контекст дисплея.
Продемонстрируем эту технику в листинге 12.2, содержащем модификацию предыдущей программы.
544 |
Глава 12. Специальные приложения |
|
|
Листинг 12.2. Проект BounceBallAdv
//////////////////////////////////////////////////////////////////////
// BounceBallAdv.cpp #include <windows.h> #include <stdio.h> #include <math.h> #include "KWnd.h" #include "resource.h"
#define Pi 3.14159265
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBall(HWND, HDC, HDC, HBITMAP, BITMAP, FLOAT, FLOAT, int); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG lpMsg;
KWnd mainWnd("Bounce ball", hInstance, nCmdShow, WndProc);
while (GetMessage(&lpMsg, NULL, 0, 0)) { TranslateMessage(&lpMsg); DispatchMessage(&lpMsg);
}
return (lpMsg.wParam);
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam)
{
static FLOAT x = 0, y = 0; // текущая позиция мяча |
|
|
||
static FLOAT dX, dY; |
// приращения координат для сдвига на новую позицию |
|
||
static int alpha = 0; // угол, определяющий вращение мяча |
|
|
||
static HDC hDC; |
|
// контекст дисплея |
|
|
static HBRUSH hBkBrush; |
// узорная кисть |
|
|
|
HBITMAP hBmpBkgr; |
|
// растр для узорной кисти |
|
|
static HBITMAP hBmpBall; // растр с изображением мяча |
|
|
||
static BITMAP bm; |
|
// параметры растра |
|
|
RECT rect; // прямоугольник клиентской области |
|
|
||
RECT rBall; // прямоугольник, ограничивающий изображение мяча |
|
|
||
static HDC hMemDcFrame; |
// совместимый контекст (контекст в памяти) |
|
||
static HBITMAP hBmpFrame; // растр для совместимого контекста |
|
|
||
static int count = 0; |
|
|
|
|
switch(uMsg) |
|
|
|
|
{ |
|
|
|
|
case WM_CREATE: |
|
|
|
|
hDC = GetDC(hWnd); |
|
|
|
|
GetClientRect(hWnd, &rect); |
|
|
||
dX = rect.right / 100.; |
|
|
||
dY = rect.bottom / 50.; |
|
|
||
// Создать таймер |
(0.1 ñåê) |
продолжение |
|
|
|
|
|
||
Анимация |
545 |
|
|
Листинг 11.2 (продолжение)
SetTimer(hWnd, 1, 100, NULL);
hBmpBkgr = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE),
MAKEINTRESOURCE(IDB_STONE));
hBkBrush = CreatePatternBrush(hBmpBkgr);
SetClassLong(hWnd, GCL_HBRBACKGROUND, (LONG)hBkBrush);
hBmpBall = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), MAKEINTRESOURCE(IDB_BALL));
GetObject(hBmpBall, sizeof(bm), (LPSTR)&bm);
hMemDcFrame = CreateCompatibleDC(hDC);
hBmpFrame = CreateCompatibleBitmap(hDC, rect.right, rect.bottom);
SelectObject(hMemDcFrame, hBmpFrame);
SetGraphicsMode(hMemDcFrame, GM_ADVANCED); break;
case WM_SIZE: GetClientRect(hWnd, &rect);
hBmpFrame = CreateCompatibleBitmap(hDC, rect.right, rect.bottom); DeleteObject(SelectObject(hMemDcFrame, hBmpFrame));
// Копирование фона в hMemDcFrame
BitBlt(hMemDcFrame, 0, 0, rect.right, rect.bottom, hDC, 0, 0, SRCCOPY);
break;
case WM_TIMER: GetClientRect(hWnd, &rect);
if (!count) { // Копирование фона в hMemDcFrame BitBlt(hMemDcFrame, 0, 0, rect.right, rect.bottom, hDC, 0, 0,
SRCCOPY);
count++;
}
// Стираем прежнюю картинку мяча
SetRect(&rBall, x, y, x + bm.bmWidth, y + bm.bmHeight); FillRect(hMemDcFrame, &rBall, hBkBrush);
//Новая позиция мяча x += dX;
y += dY; alpha += 10;
if (alpha > 360) alpha = 0;
// Если мяч достиг края окна, направление его движения изменяется if(x + bm.bmWidth > rect.right || x < 0)
dX = -dX;
if(y + bm.bmHeight > rect.bottom || y < 0) dY = -dY;
DrawBall(hWnd, hDC, hMemDcFrame, hBmpBall, bm, (int)x, (int)y, alpha); break;
case WM_DESTROY: KillTimer(hWnd, 1); ReleaseDC(hWnd, hDC);
546 |
Глава 12. Специальные приложения |
|
|
DeleteDC(hMemDcFrame);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//==================================================================== void DrawBall(HWND hwnd, HDC hdc, HDC hMemFrameDC, HBITMAP hBmp,
BITMAP bm, FLOAT x, FLOAT y, int alpha) { XFORM xform;
HRGN hRgn;
// Подготовка к выводу мяча
HDC hMemDcBall = CreateCompatibleDC(hdc); SelectObject(hMemDcBall, hBmp);
// Создаем регион отсечения
hRgn = CreateEllipticRgn(x, y, x + bm.bmWidth, y + bm.bmHeight); SelectClipRgn(hMemFrameDC, hRgn);
// Мировые преобразования для перемещения и вращения мяча
xform.eM11 = (FLOAT) cos(alpha * 2 * Pi / 360); |
//вращение |
|
xform.eM12 = (FLOAT) sin(alpha * 2 * Pi / 360); |
//вращение |
|
xform.eM21 = (FLOAT) -sin(alpha * 2 * Pi / 360); |
//вращение |
|
xform.eM22 = (FLOAT) cos(alpha * 2 * Pi / 360); |
//вращение |
|
xform.eDx = x + bm.bmWidth / 2; |
//смещение по оси x |
|
xform.eDy = y + bm.bmHeight / 2; |
//смещение по оси y |
|
//Вывод мяча в контекст hMemFrameDC SaveDC(hMemFrameDC);
BOOL ret = SetWorldTransform(hMemFrameDC, &xform); BitBlt(hMemFrameDC, -bm.bmWidth/2, -bm.bmHeight/2,
bm.bmWidth, bm.bmHeight, hMemDcBall, 0, 0, SRCCOPY); RestoreDC(hMemFrameDC, -1);
//Копирование изображения из hMemFrameDC в hdc
RECT rect; GetClientRect(hwnd, &rect);
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hMemFrameDC, 0, 0, SRCCOPY);
SelectClipRgn(hMemFrameDC, NULL); DeleteObject(hRgn); DeleteDC(hMemDcBall);
}
//////////////////////////////////////////////////////////////////////
Двойная буферизация в программе реализована на основе контекста в памяти hMemDcFrame, совместимого с контекстом дисплея. Виртуальный контекст создает ся вызовом функции CreateCompatibleDC, после чего в него выбирается при помощи функции SelectObject растр hBmpFrame, также совместимый с контекстом дисплея и имеющий размеры клиентской области окна приложения.
Инициализация контекста hMemDcFrame для копирования в него изображения фона осуществляется в блоке обработки сообщения WM_TIMER. Чтобы инициализа ция была однократной, мы используем счетчик count и вызываем функцию BitBlt только при нулевом значении счетчика. Также нужно позаботиться о новой
548 |
Глава 12. Специальные приложения |
|
|
Приемник информации от метеорадиолокатора должен получать указанные па кеты из цифрового канала связи, распаковывать полученную информацию и отображать радиолокационную картинку на своем индикаторе.
Следует, конечно, более подробно рассмотреть взаимосвязь некоторых пара метров протокола передачи.
Код угла сканирования scanAngle представлен целым 12 разрядным двоичным числом. Значение scanAngle может рассматриваться и как беззнаковое число, нахо дящееся в диапазоне от 0 до 4095 в десятичном исчислении, и как знаковое число в диапазоне от –2048 до +2047. В любом случае получается 4096 значений, которые должны покрывать максимальный сектор обзора радиолокатора, равный 360°. От сюда вес digitWeight одной единицы кода scanAngle вычисляется как 360 / 4096 = 0,087890625°.
Если PACK_PERIOD — период выдачи пакетов в миллисекундах, то максимально возможная скорость сканирования (при условии передачи каждого пакета) огра ничена следующей величиной:
speedWithPackPeriod = digitWeight / (PACK_PERIOD / 1000.)
Подставив указанное выше значение для digitWeightи значение 5 для PACK_PERIOD, получим значение speedWithPackPeriod = 17,578°/c. Но ведь протоколом определена максимальная скорость сканирования 90°/c!
Чтобы успевать передавать информацию при такой скорости сканирования, передатчик не отправляет в канал связи каждый очередной пакет, а пропускает несколько пакетов. Введем понятие коэффициента просеивания, который опреде ляется следующим выражением:
sieveCoeff = ceil(sweepSpeedDue / speedWithPackPeriod);
В этом выражении sweepSpeedDue — требуемая скорость сканирования. Например, если sweepSpeedDue = 90, то для PACK_PERIOD = 5 получаем значение
коэффициента просеивания sieveCoeff = 6. Это значит, что передатчик должен от правлять в канал связи каждый шестой пакет.
Приемник должен знать скорость сканирования sweepSpeedDueи период выдачи пакетов. Тогда он может вычислить коэффициент sieveCoeff, который используется на приемной стороне уже как коэффициент размножения луча развертки. Только в этом случае будет восстановлена исходная радиолокационная картинка (конечно, с теми потерями, которые вызваны пропуском sieveCoeff–1 лучей).
Разработка модели программного имитатора
Настоящий программный имитатор приемника информации от метеорадиолока тора является довольно сложным приложением. К тому же для его функциониро вания требуется соответствующее аппаратное обеспечение. Поскольку сейчас нас интересуют только вопросы реализации быстрого рисования, в этой главе рас сматривается построение упрощенной модели программного имитатора.
В упрощенной модели имитатора приемника информации от метеорадиоло катора мы абстрагируемся от таких проблем, как прием пакетов из цифрового канала связи и их распаковка для извлечения необходимых параметров.
Модель имитатора будет воспроизводить условную радиолокационную картинку в секторе от –60 до +60°, состоящую из четырех «колец» разного цвета.
Рисование в реальном времени |
549 |
|
|
Период передачи пакетов PACK_PERIOD будет 5 мс. Меню приложения должно позволять выбрать требуемую скорость сканирования из множества значений
{15, 30, 60, 90 } градусов в секунду.
Встроке состояния приложения должны отображаться следующие значения: а) скорость сканирования; б) коэффициент размножения лучей; в) расчетное время сканирования всего сектора (в прямом и обратном направлениях); г) фактичес кое время сканирования всего сектора.
Первая версия модели программного имитатора
Первая версия будет называться ArincReceiverBad. Смысл этого названия станет яс ным на этапе тестирования программы.
Создайте новый проект с именем ArincReceiverBad. Скопируйте из папки проекта
ToolTip(см. листинг 8.4) в папку проекта ArincReceiverBadфайлы KwndEx.hи KwndEx.cpp. Скопируйте из папки проекта KTimer (см. листинг 10.1) файл KTimer.h. Добавьте ско пированные файлы в состав проекта. Также добавьте к настройкам проекта на вклад ке Link имена библиотек comctl32.lib и winmm.lib.
Добавьте к приложению ресурс меню с идентификатором IDR_MENU1. Главное меню должно содержать пункты с атрибутами, указанными в табл. 12.1.
Таблица 12.1. Пункты главного меню
Имя пункта |
Тип пункта |
Идентификатор |
|
|
|
Скорость сканирования |
Подменю |
— |
Старт |
Команда |
IDM_START |
Ñòîï |
Команда |
IDM_STOP |
|
|
|
Подменю Скорость сканирования должно содержать пункты с атрибутами, при веденными в табл. 12.2.
Таблица 12.2. Пункты подменю «Скорость сканирования»
Имя пункта |
Тип пункта |
Идентификатор |
|
|
|
15 °/ñ |
Команда |
IDM_SPEED_15 |
30 °/ñ |
Команда |
IDM_SPEED_30 |
60 °/ñ |
Команда |
IDM_SPEED_60 |
90 °/ñ |
Команда |
IDM_SPEED_90 |
|
|
|
Добавьте в состав проекта файл ArincReceiverBad.cpp с текстом, приведенным в листинге 12.3.
Листинг 12.3. Проект ArincReceiverBad
//////////////////////////////////////////////////////////////////////
// ArincReceiverBad.cpp #include <windows.h> #include <commctrl.h> #include <Mmsystem.h> #include <stdio.h> #include <math.h>
#include "resource.h" #include "KWndEx.h"
550 |
|
|
|
Глава 12. Специальные приложения |
|
|
|
||
#include |
"KTimer.h" |
|
||
#define ID_STATUSBAR 201 |
|
|||
#define PACK_PERIOD |
5 |
// в миллисекундах |
||
#define LEFT_MARGIN |
-683 |
// angle = -60°/ñ |
||
#define RIGHT_MARGIN |
683 |
// angle = +60°/ñ |
||
#define Pi |
3.141592653589793 |
|||
HDC |
g_hDC; |
|
|
|
HWND |
hwndStatusBar; |
|
|
|
double sweepSpeedDue; // требуемая скорость развертки (градусов в секунду) double speedWithPackPeriod; // скорость развертки для периода PACK_PERIOD
int sieveCoeff; |
// коэффициент просеивания/размножения лучей развертки |
|
double sweepTimeDue; |
// расчетное время просмотра сектора (с) |
|
double sweepTimeFact; // фактическое время просмотра сектора (с) |
||
int scanAngle; |
|
// код угла сканирования в принятом пакете |
double digitWeight; // вес единицы кода scanAngle |
||
COLORREF cell[512]; // массив цветов точек дальности |
||
int X0, Y0; |
|
// начало системы координат для развертки |
double x, y; |
|
// текущие кординаты вывода точки |
KTimer timer;
typedef enum { FORTH, BACK } DIR;
DIR direction = FORTH; // направление сканирования
void UpdateStatusBar();
void CALLBACK TimerFunc(UINT, UINT, DWORD, DWORD, DWORD);
void GetPacket(); |
// Получение пакета из канала связи |
|
void GetNextAngle(); |
// Вычисление следующего значения кода угла |
|
void SetColorBeamForth(); // |
Имитация формирования массива cell |
|
|
// |
для прямого сканирования |
void SetColorBeamBack(); |
// Имитация формирования массива cell |
|
|
// |
для обратного сканирования |
void DrawBeam(); |
// Вывод луча на экран |
|
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
KWndEx mainWnd("ArincReceiverBad", hInstance, nCmdShow, WndProc, MAKEINTRESOURCE(IDR_MENU1), 100, 100, 900, 600);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
продолжение
