Здавалка
Главная | Обратная связь

Функции рисования линий



При рисовании линий активно используют значение текущей позиции пера. Чтобы узнать текущую позицию пера, вызывают функцию GetCurrentPositionEx:

 

BOOL GetCurrentPositionEx( HDC hdc, LPPOINT lpPoint);

 

Параметр lpPoint указывает на структуру типа POINT, в которую будут записаны логические координаты текущей позиции. В случае успешного выполнения функция возвращает ненулевое значение.

Чтобы нарисовать прямую линию, вызывают функцию LineTo:

 

BOOL LineTo( HDC hdc, int x, int у);

 

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

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

 

BOOL DrawLine( HDC hdc, int x0, int y0, int x, int y)

{

POINT pt;

MoveToEx(hdc, x0, y0, &pt);

return LineTo(hdc, x, y);

}

 

Эта функция рисует прямую, начиная от точки (х0, у0), до точки (х, у) и переносит текущую позицию в точку (х, у).

Задача. Нарисовать график функции 25*cos(x) для x, изменяющегося от 0 до 31.4159.

Листинг 3.8. График функции

#include <windows.h>

#include <math.h>

#include <tchar.h>

 

BOOL RegClass(WNDPROC, LPCTSTR, UINT);

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

 

HINSTANCE hInstance;

TCHAR szClassName[] = TEXT("GraphClass");

typedef struct

{

TCHAR name[10];

float x[100];

float y[100];

} FUNC;

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

MSG msg;

HWND hwnd;

hInstance = hInst;

if (!RegClass(WndProc, szClassName, COLOR_WINDOW)) return FALSE;

hwnd = CreateWindow(szClassName, TEXT("График косинуса"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0,0, hInstance, NULL);

if (!hwnd) return FALSE;

while(GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);

return msg.wParam;

}

 

BOOL RegClass(WNDPROC Proc, LPCTSTR szName, UINT brBackground)

{

WNDCLASS wc;

wc.style = CS_HREDRAW | CS_VREDRAW;

wc.cbClsExtra = wc.cbWndExtra = 0;

wc.lpfnWndProc = Proc;

wc.hInstance = hInstance;

wc.lpszClassName = szName;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(HBRUSH)(brBackground+1);

wc.lpszMenuName = NULL;

return (RegisterClass(&wc) != 0);

}

 

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

static short cx, cy;

static FUNC myfunc;

switch (msg)

{

case WM_SIZE:

{ cx = LOWORD(lParam); cy = HIWORD(lParam); return 0; }

case WM_CREATE:

{

_tcscpy(myfunc.name, TEXT("25*cos(x)"));

for (int i=0; i<100; i++)

{

myfunc.x[i]=(float)(0.31415926535*i);

myfunc.y[i]=(float)(25*cos(myfunc.x[i]));

}

return 0;

}

case WM_PAINT:

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

//Задаем область для рисования графика

int x0, xc, y0, yc, amp;

x0 = cx/10; //Левый край графика

xc = cx-x0; //Правый край графика

y0 = cy/10; //Верхний край графика

yc = cy/2; //Середина графика по оси у

amp = yc-y0; //Амплитуда графика на экране

float ymax = 0., //Максимум функции

ymin = 0.; //Минимум функции

//Выводим название функции

TextOut(hdc, x0+4, y0/3, myfunc.name, _tcslen(myfunc.name));

//Выводим название переменной

TextOut(hdc, xc+x0/2, yc+y0/10, TEXT("t"), 1);

//Рисуем ось ординат

MoveToEx(hdc, x0, cy-y0+y0/2, NULL);

LineTo(hdc, x0, y0-y0/2);

//Рисуем ось абсцисс

MoveToEx(hdc,x0, yc, NULL);

LineTo(hdc, xc+x0/2, yc);

//Определяем максимум и минимум функции

for (int i=0; i<100; i++)

{

float ycurr = myfunc.y[i];

if (ymax<ycurr) ymax=ycurr;

else

if (ymin>ycurr) ymin=ycurr;

}

if (ymax<fabs(ymin)) ymax=(float)fabs(ymin);

//Устанавливаем масштаб по оси у

float dy=(float)amp/ymax;

//Устанавливаем масштаб по оси х

float dx=(float)(xc-x0)/(myfunc.x[99]-myfunc.x[0]);

//Для рисования создаем перо синего цвета

HPEN hPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));

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

//Устанавливаем курсор в первую точку графика

MoveToEx(hdc, x0, (int)(yc-dy*myfunc.y[0]),NULL);

//Выводим линии от предыдущей точки к текущей

for(int i=1; i<100; i++)

{

//Вычисляем координаты текущей точки

int xcurr=(int)(dx*(myfunc.x[i]-myfunc.x[0])+x0),

ycurr=(int)(yc-dy*myfunc.y[i]);

//Рисуем прямую до текущей точки

LineTo(hdc, xcurr, ycurr);

}

SelectObject(hdc, hOldPen);

DeleteObject(hPen);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY: { PostQuitMessage(0); return 0;}

}

return DefWindowProc(hwnd, msg, wParam, lParam);

}

 

В приложении описывают тип структуры с именем FUNC:

 

typedef struct {

char name[10];

float x[100];

float y[100];

} FUNC;

 

Структура такого типа будет содержать поле name из 10 символов, поле х из 100 вещественных чисел (для аргумента функции) и поле y из 100 вещественных чисел (для значений функции).

При создании окна структуру myfunc типа FUNC заполняют данными. В поле name записывают название функции:

 

strcpy(myfunc.name, "25*cos(x)");

 

А в поля х и y записывают значения соответственно аргумента и функции:

 

for (int i=0; i<100; i++)

{

myfunc.x[i]=(float)(0.314159265354);

myfunc.y[i]=(float)(25*cos(myfunc.x[i]));

}

 

Обработка сообщения WM_PAINT состоит из следующих основных шагов:

1. Задают область для рисования графика в рабочей области:

 

int х0 = сх/10, //Левый край графика

xc = сх-х0, //Правый край графика

у0 = су/10, //Верхний край графика

ус = су/2, //Середина графика по оси у

ymax = 0, //Максимум функции

ymin = 0, //Минимум функции

amp = ус-у0; //Амплитуда графика

 

Параметры области рисования графика здесь выбраны произвольным образом. Например, левый край графика с таким же успехом можно было выбрать вдвое меньше – х0 = сх/20.

2. Далее выводят тексты названия функции:

 

TextOut(hdc, х0+4, у0/3, myfunc.name, strlen(myfunc.name));

 

и названия переменной:

 

TextOut(hdc, xc+х0/2, ус+у0/10, "t", 1);

 

3. Рисуют прямые линии осей координат:

 

MoveToEx( hdc, х0, су-у0+у0/2, NULL);

LineTo( hdc, х0, у0-у0/2);

MoveToEx( hdc, х0, ус, NULL);

LineTo( hdc, xc+x0/2, ус);

 

4. Размах графика по вертикали должен быть пропорционален максимальному и минимальному значениям функции.

Поэтому сначала вычисляют максимум и минимум функции:

 

for (int i=0; i<100; i++)

{

int ycurr=(int)myfunc.y[i];

if (ymax<ycurr) ymax=ycurr;

else

if (ymin>ycurr) ymin=ycurr;

}

 

Выбирают максимальный размах по вертикали:

 

if (ymax<abs(ymin)) ymax=abs(ymin);

 

Далее вычисляют значение коэффициента масштабирования по вертикали:

 

dy=(float)amp/ymax;

 

5. Размах графика по горизонтали должен быть пропорционален максимальному и минимальному значениям аргумента. Значение коэффициента масштабирования по горизонтали вычисляют по формуле

 

dx=(float)(xc-x0)/(myfunc.x[99]-myfunc.x[0]);

 

6. Для рисования создают перо синего цвета:

 

HPEN hPen=CreatePen( PS_SOLID, 2, RGB(0, 0, 255));

 

Цвет и толщина пера выбраны произвольным образом. Далее в контекст выбирают созданное перо:

 

HPEN hOldPen=SelectObject( hdc, hPen );

 

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

7. Теперь начинают рисовать график функции.

Для этого первоначально устанавливают текущую позицию в точку началя графика:

 

MoveToEx( hdc, х0, (int)(yc-dy*myfunc.y[0]), NULL);

 

Очевидно, что она совпадает с левым краем х0 области рисования графика и отклонением по вертикали на целое количество пикселей dy*myfunc.y[0] от средней линии ус. Знак минус отклонения обусловлен тем, что координата y на экране по умолчанию отсчитывается сверху вниз.

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

 

for(i=1; i<100; i++)

{

Int xcurr=(int)(dx*(myfunc.x[i]-myfunc.x[0])+x0),

ycurr=(int)(yc-dy*myfunc.y[i]);

LineTo(hdc, xcurr, ycurr);

}

 

8. Завершив рисование созданным пером в контекст выбирают предыдущее перо и удаляют созданное:

 

SeledObject(hdc.hOldPen);

DeleteObject(hPen);

 

Продолжим изучение функций рисования линий.

Функция Arc рисует дугу эллипса или окружности:

 

BOOL Arc( HDC hdc, int l, int t, int r,

int b, int x0, int y0, int x, int у);

 

Следующий рисунок истолковывает геометрический смысл параметров функции Arc.

Рис. 3.1. Геометрическая интерпретация параметров функции Arc

Представим, что эллипс вписан в прямоугольник, левый верхний угол которого определяется координатами (l, t), а правый нижний угол – (r, b). Здесь логические координаты l и r отсчитываются по горизонтали, t и b – по вертикали. Тогда воображаемый центр эллипса определяется равенствами хс = (1+r)/2 и yc = (t+b)/2. Начало дуги эллипса определяется пересечением эллипса с воображаемой прямой линией, проведенной из центра эллипса (хс, ус) в точку (х0, у0). Конец дуги определяется аналогично – как пересечение эллипса с воображаемой прямой линией, проведенной из центра эллипса в точку (х, у). Дуга рисуется от начальной точки к конечной, в направлении против хода Часовой стрелки. Если начальная и конечная точки дуги совпадают, то рисуется полный эллипс. Функция Arc игнорирует текущую позицию.

В случае успешного выполнения функция возвращает ненулевое значение.

При вызове функции Arc аргументы l, t, r и b задают в зависимости от места расположения эллипса, и их выбор обычно не вызывает затруднений. Для выбора значений аргументов х0, у0, х и y удобно эти точки расположить на дуге эллипса и использовать формулы расчета в полярных координатах. Например, координата любой точки (х, у) дуги эллипса на рис. 3.1 определяется по формуле

 

x=xc+0.5*(r-l)*cos(j)

y=yc-0.5*(b-t)*sin(j)

 

Здесь j – угол наклона прямой, проведенной от центра эллипса в точку (х, у). Угол отсчитывают против хода часовой стрелки.

Функция Polyline предназначена для рисования ломаных линий:

 

BOOL Polyline(HDC hdc, CONST POINT *lppt, int cPoints);

 

Она соединяет прямыми линиями точки, координаты которых указаны в массиве структур типа POINT. Параметр lppt указывает на массив структур POINT, cPoints равен количеству точек в массиве и должен быть больше 1. В случае успешного выполнения функция возвращает ненулевое значение. Функция Polyline игнорирует текущую позицию. Прямые рисуют от точки первого элемента массива к точке второго элемента и т. д. Если ломаная линия не замкнута, ее последняя точка не рисуется.

Задача. Поверхность, обрамленную эллипсом, разделить на 3 сектора с условными долями 62.5, 12.5 и 25%.

Листинг 3.9. Эллиптическая диаграмма

#include <windows.h>

#include <math.h>

 

#define PI 3.1415926535

BOOL RegClass(WNDPROC, LPCTSTR, UINT);

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

 

HINSTANCE hInstance;

TCHAR szClass[] = TEXT("LinesClass");

 

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)

{

MSG msg;

HWND hwnd;

hInstance = hInst;

if (!RegClass(WndProc, szClass, COLOR_WINDOW)) return FALSE;

hwnd = CreateWindow(szClass, TEXT("Эллиптическая диаграмма"), WS_OVERLAPPEDWINDOW | WS_VISIBLE,

CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

if (!hwnd) return FALSE;

while(GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);

return msg.wParam;

}

 

BOOL RegClass(WNDPROC Proc, LPCTSTR szName, UINT brBackground)

{

WNDCLASS wc;

wc.style = CS_HREDRAW | CS_VREDRAW;

wc.cbClsExtra = wc.cbWndExtra = 0;

wc.lpfnWndProc = Proc;

wc.hInstance = hInstance;

wc.lpszClassName = szName;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)(brBackground+1);

wc.lpszMenuName = NULL;

return (RegisterClass(&wc) != 0);

}

 

BOOL DrawLine( HDC hdc, int x0, int y0, int x, int y)

{

MoveToEx(hdc, x0, y0, NULL);

return LineTo(hdc, x, y);

}

 

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

static short cx, cy;

switch (msg)

{

case WM_SIZE:

{ cx = LOWORD(lParam); cy = HIWORD(lParam); return 0; }

case WM_PAINT:

{

PAINTSTRUCT ps;

HDC hdc = BeginPaint(hwnd, &ps);

//Задаем область для рисования эллипса

int l = cx/10, //Левый край эллипса

r = cx-l, //Правый край эллипса

t = cy/10, //Верхний край эллипса

b = cy-t, //Нижний край эллипса

yc = cy/2, //Центр эллипса по оси у

xc = cx/2; //Центр эллипса по оси х

//Задаем координаты точек дуги

int x0 = (int)(xc+0.5*(r-l)),

у0=yc,

x = (int)(xc+0.5*(r-l)*cos(2.*PI*0.625)),

y=(int)(yc-0.5*(b-t)*sin(2.*PI*0.625));

//Рисуем прямую линию

DrawLine(hdc, xc, yc, x0, у0);

//Рисуем дугу эллипса

Arc(hdc, l, t, r, b, x0, у0, x, y);

x0=x; у0=y; //Запоминаем координаты точки

//Выводим текст

x = (int)(xc+0.4*cx*cos(2.*PI*0.3125));

y = (int)(yc-0.4*cy*sin(2.*PI*0.3125));

TextOut(hdc, x, y, TEXT("62,5%"), 6);

//Рисуем прямую линию

DrawLine(hdc, xc, yc, x0, у0);

x=(int)(xc+0.5*(r-l)*cos(2.*PI*0.75));

y=(int)(yc-0.5*(b-t)*sin(2.*PI*0.75));

//Рисуем дугу эллипса

Arc(hdc, l, t, r, b, x0, у0, x, y);

x0=x; у0=y; //Запоминаем координаты точки

//Выводим текст

x=(int)(xc+0.4*cx*cos(2.*PI*0.6875));

y=(int)(yc-0.4*cy*sin(2.*PI*0.6875));

TextOut(hdc, x, y, TEXT("12,5 %"), 6);

//Рисуем прямую линию

DrawLine(hdc, xc, yc, x0, у0);

x=(int)(xc+0.5*(r-l)*cos(2.*PI));

y=(int)(yc-0.5*(b-t)*sin(2.*PI));

//Рисуем дугу эллипса

Arc(hdc, l, t, r, b, x0, у0, x, y);

//Выводим текст

x=(int)(xc+0.4*cx*cos(2.*PI*0.875));

y=(int)(yc-0.4*cy*sin(2.*PI*0.875));

TextOut(hdc, x, y, TEXT("25 %"), 4);

EndPaint(hwnd, &ps);

return 0;

}

case WM_DESTROY: { PostQuitMessage(0); return 0; }

}

return DefWindowProc(hwnd, msg, wParam, lParam);

}

 

В этом приложении новым является только расчет значений координат точек дуги:

 

x=(int)(xc+0.5*(r-l)*cos(2.*PI*0.625));

y=(int)(yc-0.5*(b-t)*sin(2.4PI*0.625));

 

Здесь значение 2.*Р1*0.625 в радианах задает 62.5 % от 360 градусов. Остальные операторы обсуждались ранее и не требуют пояснений.







©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.