Визуализация цифровых данных




Вывод графической информации на дисплей

 

Хотя некоторым приложений Windows для организации полного интерфейса пользователя нужны только окна, диалоги и управляющие элементы, многим приложениям (например, программам – осциллографам или генераторам функций) требуется наличие графических средств. Эта простейшая графика может быть в форме линий, кривых, фигур, текста и т.д.

Для предоставления приложениям графических функциональных возможностей Windows имеет стандартный набор функций, называемый интерфейсом графических устройств – GDI. Функции GDI предоставляют возможности рисования, которые не зависят от используемого устройства вывода. Например, одни и те же функции можно использовать для организации вывода на дисплей или принтер. Аппаратная независимость реализуется через использование драйверов устройств, которые переводят функции GDI в команды, воспринимаемые используемым устройством вывода. Это означает, что не нужно беспокоиться о том, как конкретное устройство (например, определенная марка принтеров) работает с графическим образом. Подобная абстракция аппаратной части является очень распространенной. Ценой универсальности являются достаточно ограниченные возможности по работе с графикой и невысокое быстродействие графических команд. Насыщенные визуальными данными программы следует разрабатывать с использованием специализированных программных средств: OpenGL, GLES, Direct3D и т.п.

В отличие от традиционных графических программ DOS программы Windows никогда не выводят элементы изображения непосредственно на экран или на принтер, а записывают их в логическую сущность, называемую контекстом устройства. Контекст устройства – это виртуальная поверхность с присущими ей атрибутами, такими как перо, кисть, шрифт, цвет фона, цвет текста и текущая позиция. Для приложения, независимо от того, какое это на самом деле устройство, все контексты устройства выглядят аналогично.

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

1. получение или создание контекста отображения;

2. установка необходимых атрибутов в контексте отображения;

3. выполнение операций рисования;

4. освобождение либо удаление контекста отображения.

Последнее действие (освобождение либо удаление контекста отображения) должно быть обязательно выполнено, так как контекст – очень ресурсоёмкая структура данных. Если создать множество контекстов, а затем не удалить (или не освободить) эти контексты, система станет работать нестабильно из-за нехватки ресурсов, единственным выходом станет ее перезагрузка.

Рассмотрим особенности работы устройств вывода информации на примере программы построения круговой диаграммы, окно которой показано на рисунке 2.2.1.

Рисунок 2.2.1 – Тестовая программа, выполняющая визуализацию данных

Программа основана на примере простейшей программы Windows, рассмотренной ранее. Текст на языке C++ рабочей программы выглядит следующим образом (для удобства новый код выделен жирным шрифтом):

 

#define _USE_MATH_DEFINES

#include <math.h>

#include <stdio.h>

#include <time.h>

 

// Стандартный включаемый файл Windows

#include <windows.h>

 

const float g_data[] = { 10.0f, 4.0f, 30.0f, 12.0f, 26.0f, 5.0f, 18.0f };

Struct Brush

{

int fnStyle;

COLORREF clrref;

};

unsigned int g_iNumOfPies;

Brush* g_pBrushes;

 

// Прототип функции обратного вызова для обработки сообщений

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

 

Void Diagram(HDC hDC,

Int x,

Int y,

Int r,

Bool fShadow,

const float* pData,

const Brush* pBrushes,

unsigned int iNumOfPies);

Void DiagramPie(HDC hDC,

Int x,

Int y,

Int r,

Float s,

Float e,

Int fnStyle,

COLORREF clrref,

const char* szLabel);

Void ShadyDiagram(HDC hDC,

Int x,

Int y,

Int r,

Int iShadowDepth,

const float* pData,

const Brush* pBrushes,

unsigned int iNumOfPies);

 

// Функция вызывается автоматически, когда программа запускается

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

{

HWND hWnd;

MSG msg;

WNDCLASSEX wndclass;

 

// Настройка класса окна

wndclass.cbSize = sizeof(WNDCLASSEX);

wndclass.style = CS_HREDRAW | CS_VREDRAW;

wndclass.lpfnWndProc = WndProc;

wndclass.cbClsExtra = 0;

wndclass.cbWndExtra = 0;

wndclass.hInstance = hInstance;

wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

wndclass.lpszMenuName = NULL;

wndclass.lpszClassName = “Window Class”; // Имя класса

wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

 

// Регистрация класса окна

if(RegisterClassEx(&wndclass) == 0)

{

// Сбой программы, выход

return 0;

}

 

// Создание окна

hWnd = CreateWindowEx(

WS_EX_OVERLAPPEDWINDOW,

«Window Class», // Имя класса

«Построение круговой диаграммы», // Текст заголовка

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,

CW_USEDEFAULT,

700,

580,

NULL,

NULL,

hInstance,

NULL);

 

// Отображение окна

ShowWindow(hWnd, iCmdShow);

 

// Обработка сообщений, пока программа не будет прервана

while(GetMessage(&msg, NULL, 0, 0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

 

return (int)msg.wParam;

}

 

// Функция обратного вызова для обработки сообщений

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

HDC hDC;

PAINTSTRUCT ps;

unsigned int I;

 

switch(iMsg)

{

case WM_CREATE:

g_iNumOfPies = sizeof(g_data) / sizeof(g_data[0]);

g_pBrushes = new Brush[g_iNumOfPies];

srand((unsigned int)time(NULL));

for (I = 0u; I < g_iNumOfPies; ++i)

{

g_pBrushes[i].fnStyle = rand() % 7;

g_pBrushes[i].clrref = RGB(rand() % 0xFF, rand() % 0xFF, rand() % 0xFF);

}

break;

// Вызывается, когда окно обновляется

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps);

ShadyDiagram(hDC, 320, 260, 200, 10, g_data, g_pBrushes, g_iNumOfPies);

EndPaint(hWnd, &ps);

break;

// Вызывается, когда пользователь закрывает окно

case WM_DESTROY:

If (g_pBrushes)

delete[] g_pBrushes;

 

PostQuitMessage(0);

break;

default:

return DefWindowProc(hWnd, iMsg, wParam, lParam);

}

 

return 0;

}

 

void Diagram(HDC hDC, int x, int y, int r, bool fShadow, const float* pData, const Brush* pBrushes, unsigned int iNumOfPies)

{

HPEN hOldPen, hPen;

char szLabel[BUFSIZ];

int dx, dy;

float e, s;

float k;

unsigned int I;

for (I = 0u, s = 0.0f; I < iNumOfPies; ++i)

s += pData[i];

if (s == 0.0f)

return;

k = (float)(2.0f * M_PI / s);

hPen = CreatePen(PS_SOLID, 2, 0);

hOldPen = (HPEN)SelectObject(hDC, hPen);

for (I = 0u, s = 0.0f; I < iNumOfPies; ++i)

{

e = s + pData[i] * k;

if (I == 1 || I == 3 || I == 6)

{

dx = int(cos((e + s) / 2) * r * 0.25f);

dy = int(sin((e + s) / 2) * r * 0.25f);

}

Else

{

dx = dy = 0;

}

sprintf(szLabel, “%d - %.02f (%.0f%%)”, I + 1u, (float)pData[i], (float)(pData[i] * k * 50.0f / M_PI));

If (fShadow)

DiagramPie(hDC, x + dx, y + dy, r, s, e, 6, 0, NULL);

Else

DiagramPie(hDC, x + dx, y + dy, r, s, e, pBrushes[i].fnStyle, pBrushes[i].clrref, szLabel);

s = e;

}

SelectObject(hDC, hOldPen);

DeleteObject(hPen);

return;

}

void DiagramPie(HDC hDC, int x, int y, int r, float s, float e, int fnStyle, COLORREF clrref, const char* szLabel)

{

HBRUSH hBrush, hOldBrush;

float dx, dy;

float m;

hBrush = CreateHatchBrush(fnStyle, clrref);

hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);

Pie(

HDC,

X – r,

Y – r,

x + r,

y + r,

x + int(cosf(e) * r),

y + int(sinf(e) * r),

x + int(cosf(s) * r),

y + int(sinf(s) * r)

);

SelectObject(hDC, hOldBrush);

DeleteObject(hBrush);

if (!szLabel)

return;

m = (e + s) / 2.0f;

dx = cosf(m) * r;

dy = sinf(m) * r;

MoveToEx(

HDC,

x + int(dx * 0.5f),

y + int(dy * 0.5f),

NULL

);

LineTo(

HDC,

x + int(dx * 1.2f),

y + int(dy * 1.2f)

);

TextOut(

HDC,

x + int(dx * 1.25f) + 5,

y + int(dy * 1.25f) – 15,

SzLabel,

(int)strlen(szLabel)

);

return;

}

void ShadyDiagram(HDC hDC, int x, int y, int r, int iShadowDepth, const float* pData, const Brush* pBrushes, unsigned int iNumOfPies)

{

Diagram(hDC, x + iShadowDepth, y + iShadowDepth, r, true, pData, pBrushes, iNumOfPies);

Diagram(hDC, x, y, r, false, pData, pBrushes, iNumOfPies);

return;

}

 

Подробно рассмотрим ее работу. Вывод на экран осуществляется при обработке сообщения WM_PAINT:

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps);

ShadyDiagram(hDC, 320, 260, 200, 10, g_data, g_pBrushes, g_iNumOfPies);

EndPaint(hWnd, &ps);

break;

 

Почему нельзя просто вывести данные в произвольном месте программы? Современные операционные системы, как правило, являются многозадачными, что означает возможность одновременной работы нескольких программ. Допустим, Вы отобразили в окне программы какой-то рисунок, потом окно частично перекрывается другим окном или полностью исчезает с экрана. Затем через некоторое время верхнее окно сдвигается. Казалось бы, операционная система должна самостоятельно восстановить пропавшую часть изображения. Увы, но это не так.

Существует взаимодействие «документ – вид», используемое при графических построениях с помощью электронных устройств. Документ представляет некие данные, невидимые для пользователя, хранимые в памяти компьютера, которые пользователь может редактировать, загружать и сохранять, используя диск. С другой стороны вид – временные данные, которые связаны с документом, и которые пользователь ассоциирует с ним. Например, текстовый документ программы «Блокнот» – массив байт, каждый байт представляет один символ, хранимый в оперативной памяти. Видом этого документа является графическое изображение на экране монитора с помощью шрифта определенного типа, размера, цвета и т.д. Вид можно всегда восстановить по данным документа, используя предопределенный программистом алгоритм, что и осуществляется при обработке сообщения WM_PAINT.

Функция BeginPaint возвращает контекст отображения для окна hWnd:

HDC WINAPI BeginPaint(HWND hWnd, PAINTSTRUCT FAR *lpps);

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

Контекст отображения, полученный с помощью функции BeginPaint, необходимо освободить перед завершением обработки сообщения WM_PAINT, вызвав функцию EndPaint с теми же параметрами:

void WINAPI EndPaint(HWND hWnd, PAINTSTRUCT FAR *lpps);

Функции BeginPaint и EndPaint можно использовать только внутри обработчика сообщения WM_PAINT. Если требуется рисовать при обработке других сообщений, получить контекст отображения можно с помощью функции GetDC() и освободить функцией ReleaseDC();

Функция GetDC возвращает контекст отображения для окна с идентификатором hwnd:

HDC WINAPI GetDC(HWND hWnd);

Функция ReleaseDC освобождает контекст изображения hdc, полученный для окна hwnd:

int WINAPI ReleaseDC(HWND hwnd, HDC hdc);

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

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

const float g_data[] = { 10.0f, 4.0f, 30.0f, 12.0f, 26.0f, 5.0f, 18.0f };

Struct Brush

{

int fnStyle;

COLORREF clrref;

};

unsigned int g_iNumOfPies;

Brush* g_pBrushes;

где g_data – массив некоторых данных, по которым строится диаграмма;

структура Brush будет использоваться для хранения типов кистей (различные виды штриховки) и их цветов;

g_iNumOfPies – количество значений, по которым строится диаграмма;

g_pBrushes – кисти, массив будет динамически создан при запуске программы и заполнен случайными значениями, разумеется размеры массивов g_data и g_pBrushes одинаковы и равны g_iNumOfPies.

После успешного создания окна массив кистей создается и заполняется:

case WM_CREATE:

g_iNumOfPies = sizeof(g_data) / sizeof(g_data[0]);

g_pBrushes = new Brush[g_iNumOfPies];

srand((unsigned int)time(NULL));

for (I = 0u; I < g_iNumOfPies; ++i)

{

g_pBrushes[i].fnStyle = rand() % 7;

g_pBrushes[i].clrref = RGB(rand() % 0xFF, rand() % 0xFF, rand() % 0xFF);

}

Зачем он нужен? Например, пользователь поменял размер окна, всю диаграмму придется перерисовать и при этом желательно, чтобы, цвета всех сегментов остались такими же какими были. Если программу запустить повторно, диаграмма перекрасится.

Все ресурсы, которые мы запрашиваем в программе, например, оперативную память под массив данных, должны быть освобождены при завершении программы. Это удобно сделать при обработке противоположного WM_CREATE сообщения WM_DESTROY:

case WM_DESTROY:

If (g_pBrushes)

delete[] g_pBrushes;

Все действия по построению диаграммы осуществляются при обработке сообщения WM_PAINT:

case WM_PAINT:

hDC = BeginPaint(hWnd, &ps);

ShadyDiagram(hDC, 320, 260, 200, 10, g_data, g_pBrushes, g_iNumOfPies);

EndPaint(hWnd, &ps);

break;

Весь код инкапсулирован в три пользовательские функции:

ShadyDiagram – построение полной диаграммы;

Diagram – построение части диаграммы (основной или тени);

DiagramPie – построение одного сегмента диаграммы.

ShadyDiagram просто вызывает два раза функцию Diagram передавая немного разные на величину iShadowDepth (глубина тени) координаты центра для диаграммы и ее тени:

void ShadyDiagram(HDC hDC, int x, int y, int r, int iShadowDepth, const float* pData, const Brush* pBrushes, unsigned int iNumOfPies)

{

Diagram(hDC, x + iShadowDepth, y + iShadowDepth, r, true, pData, pBrushes, iNumOfPies);

Diagram(hDC, x, y, r, false, pData, pBrushes, iNumOfPies);

return;

}

Функции построения одного сегмента имеет следующий прототип:

void DiagramPie(HDC hDC, int x, int y, int r, float s, float e, int fnStyle, COLORREF clrref, const char* szLabel);

В функцию передается контекст устройства, координаты центра x и y (по умолчанию ось x направлена вправо, ось y вниз, начало координат в левом верхнем углу рабочей области окна), радиус, начальный и конечный углы в радианах между которыми строится сегмент s и e, стиль кисти и ее цвет, текстовая метка для сегмента. Если szLabel равно NULL, метка не отображается (необходимо для сегментов тени).

Создание штриховой кисти осуществляется следующим образом:

hBrush = CreateHatchBrush(fnStyle, clrref);

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

hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);

Функция универсальная для кистей, перьев и т.п., поэтому требуется явное преобразование типа. Возвращает предыдущую используемую кисть. Зачем ее сохранять в переменной? Кисть, как и любой другой ресурс, следует удалить после использования.

DeleteObject(hBrush);

Существует правило, справедливое для различных областей программирования – нельзя удалять что-то используемое в данный момент. Поэтому перед удалением, следует задать другую кисть текущей, например, предыдущую. Такой же подход примеряется в функции Diagram() при смене пера.

Все последующие команды вывода используют прямоугольную декартовую систему координат, поэтому для преобразования угловых величин в прямоугольные координаты используются тригонометрические функции cos() и sin().

 

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-13 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: