Обществом преподавателей информатики замечено, что очень многие, при изучении нового языка программирования, прежде всего интересуются его графическими возможностями. Видимо, ещё с детства в нас не остыл интерес к красивым разноцветным кружочкам и овалам. Как вы и думали, API даёт в этом плане огромнейшую свободу, ибо всё, что знает Windows о рисовании, она знает от API.
Разноцветные геометрические фигуры, которые можно заливать любым цветом, эллипсы, окружности, прямоугольники, линии. Во введении я уже говорил про кисти и перья. Эта важнейшие особенности API дают нам возможность заливать фигуры не только сплошным покровом, а линии делать пунктирными и штрих-пунктирными.
С текстом мы вроде как разобрались, теперь переходим к нашим любимым графическим примитивам. Так как в этой главе мы будем изучать почти все графические функции, предлагаю сделать заготовочку, в которую можно примерять новые изученные функции.
КАК и где вставлять графические функции?
Сам текст программы ничем не отличается от предыдущих. Рассмотрим лишь сообщение WM_PAINT. Я покажу куда рисовать.
case WM_PAINT :
hdc=BeginPaint(hWnd, &ps);
//здесь можно вставить какие-нибудь функции рисования
.
//обновляем окно
ValidateRect(hWnd, NULL);
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;
Графические функции GDI:
1. Вывод точки. SetPixel устанавливает заданный цвет в точке с указанными координатами:
COLORREF SetPixel(HDC hDC, int x, int y, COLORREF crColor);
Пример:
SetPixel(hDC, 10,10, RGB(0,0,0));
Функция GetPixel соответственно возвращает цвет в заданных координатах.
COLORREF Getpixel(hDC, int x, int y);
2. Рисование линий.
BOOL LineTo(hDC, int x, int y);
Функция рисует линию от текущей позиции до места, указанного в аргументах. Чтобы изменить тип линии (толщину, стиль)- меняется тип пера. Но об этом позже.
Так как в отличие от многих других подходов, в GDI нет функции рисования линии от одного указанного места до другого, её можно создать самому. Она будет соединять линией точки с координатами: x1,y1 и x2,y2.
BOOL Line(HDC hdc, int x1, int y1, int x2, int y2)
<
MoveToEx(hdc, x1, y1, NULL); //сделать текущими координаты x1, y1
return LineTo(hdc, x2, y2);
>
3. Дуга
BOOL Arc(hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2);
Первые четыре аргумента — левый верхний и правый нижний углы прямоугольника, в который вписан эллипс. Остальные значения — координаты точек, от которых будут проведены прямые к центру эллипса. В местах пересечения первой и второй прямой с радиусом эллипса, начинается и кончается дуга.
4. Прямоугольник. По умолчанию прозрачный, а вообще, тип его заливки определяется текущей кистью. По умолчанию она тоже прозрачная.
BOOL Rectangle(hDC, int left, int top, int right, int bottom); //аргументы — это коордианты левого верхнего и правого нижнего углов
5. Закруглённый прямоугольник. Его можно использовать, как импровизированную кнопку, если не лень возиться.
BOOL RoundRect(hDC, int left, int top, int right, int bottom, int width, int height);
Первые пять параметров совпадают с параметрами предыдущей фукнции. Далее width и height задают ширину и высоту эллипса, дуги которого ограничивают прямоугольник.
6. Кисти. Самое время познакомиться с кистями, так как фигуры, которые пойдут дальше выглядят лучше закрашенными. Мы уже немного затронули эту тему во вводной части. Теперь рассмотрим как задать свой стиль кисти. Как и setfillstyle() в DOS, кисть закрашивает какую-то область в какой-то цвет. В зависимости от кисти, она может делать это в полосочку, в клеточку, по диагонали.
Есть два способа объявить кисть. Первый — задать сплошную заливку, второй — указать стиль. Для этого существуют соответственно функции: CreateSoldBrush() и CreateHatchBrush().
Пример:
HBRUSH hBrush; //создаём объект-кисть
CreateSolidBrush(RGB(255,0,67)); //задаём сплошную кисть, закрашенную цветом RGB
SelectObject(hdc, hBrush); //делаем кисть активной
А вот как объявить не сплошную кисть:
CreateHatchBrush(int fnStyle, RGB(r,g,b));
Аргумент fnStyle принимает ряд константных значений:
HS_DIAGONAL — штрихует по диагонали
HS_CROSS — клеточка
HS_DIAGCROSS — диагональная сетка
HS_FDIAGONAL — по диагонали в другую сторону
HS_HORIZONTAL — горизонтальная «тельняшка»
HS_VERTICAL — вертикальный «забор»
HBRUSH hBrush1;
CreateHatchBrush(int fnStyle, RGB(r,g,b));
SelectObject(hdc, hBrush1); //делаем кисть активной
Вот махонький пример сообщения WM_PAINT с использованием кистей.
//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);
HBRUSH hBrush;
hBrush=CreateHatchBrush(HS_FDIAGONAL, RGB(255,0,0));
SelectObject(hdc,hBrush);
Ellipse(hdc, 100,100,200,300); //эллипс будет заштрихован
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;
7. Перья. Они задают стиль линий, как и setlinestyle в DOS. Линия может быть жирной и тонкой, прерывистой и штрих-пунктирной. Всё предусмотрено. Это очень удобно для создания графиков функций, когда на график накладывается сетка, рисуются оси и выводится сама функция. Вы можете сказать, что это касается только математиков! Но почти любая фирма, что бы она не производила, иногда проводит презентации. На графике можно показать рост внешнего капитала, объём продаж и многое другое.
HPEN hPen; //Объявляется кисть
CreatePen(fnPenStyle, int width, RGB(r,g,b)); //Создаётся объект
SelectObject(hdc, hPen); //Объект делается текущим
fnStyle может принимать следующие значения:
PS_SOLD — сплошная
PS_DASH — состоящая из точек
PS_DOT — состоящая из тире
PS_DASHDOT — «точка-тире»
PS_DASHDOTDOT — «тире-точка-точка-тире»
PS_NULL — невидимая
PS_INSIDEFRAME — обводка замкнутых фигур
И пример будет такой:
//сообщение рисования
case WM_PAINT :
//начинаем рисовать
hdc=BeginPaint(hWnd, &ps);
HPEN hPen1, hPen2, hPen3; //объявляем сразу три объекта-пера
hPen1=CreatePen(PS_DASHDOT, 1, RGB(0,0,255)); //создаём всё три
hPen2=CreatePen(PS_DASH, 1, RGB(255,0,255));
hPen3=CreatePen(PS_DOT, 1, RGB(0,128,256));
SelectObject(hdc, hPen1); //но в одним момент времени может быть только 1
Rectangle(hdc, 10,10,100,100); //рисуем фигуру соответствующим пером
SelectObject(hdc, hPen2); //меняем перо
Ellipse(hdc, 100,100,200,300); //рисуем другим пером
SelectObject(hdc, hPen3);
LineTo(hdc, 200,100);
ValidateRect(hWnd, NULL);
//заканчиваем рисовать
EndPaint(hWnd, &ps);
break;
Важно понять, что можно создавать хоть 10 перьев с помощью CreatePen, но применить в данный момент времени можно только 1 из них. Для этого и нужен SelectObject, чтобы окно поняло какую кисть в настоящий момент мы достаём из этюдника GDI.
6. Закрашенный прямоугольник
int FillRect(HDC hDC, CONST RECT *lprc, HBRUSH hbr);
lprc — закрашиваемый прямоугольник типа RECT.
hbr — кисть
Вот пример-фрагмент WM_PAINT:
RECT r; //объявляем экзмепляр структуры RECT — координаты прямоугольника.
r.left=100; //левый верхний угол
r.top=100;
r.right=200; //правый нижний
r.right=300;
А вот и первый пример программы подоспел! Нарисуем что-то очень красивое — то, что мы уже умеем.
В этом примере мы освежим методы работы со шрифтами, поменяв стандартный системный шрифт на свой, затем нарисуем прямоугольник, заполненный красным цветом, зелёный эллипс и прямоугольник с закруглёнными краями жёлтого цвета. Естественно, что для того, чтобы их заполнить нам понадобятся кисти, а для синей рамки на жёлтом прямоугольнике понадобятся перья. Всё, что есть в этой программе вы уже умеете. Мы просто закрепляем теорию на практике.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
char szProgName[]=»Имя программы»;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd;
MSG lpMsg;
WNDCLASS w;
w.lpszClassName=szProgName; //имя программы — объявлено выше
w.hInstance=hInstance; //идентификатор текущего приложения
w.lpfnWndProc=WndProc; //указатель на функцию окна
w.hCursor=LoadCursor(NULL, IDC_ARROW); //загружаем курсор
w.hIcon=0;
w.lpszMenuName=0;
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //цвет фона окна
w.style=CS_HREDRAW|CS_VREDRAW;
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать класс окна — выходим
if(!RegisterClass(&w))
return 0;
//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName, //Имя программы
«Грфические возможности Win32 API», //Заголовок окна
WS_OVERLAPPEDWINDOW, //Стиль окна — перекрывающееся
100, //положение окна на экране по х
100, //положение по у
500, //ширина
400, //высота
(HWND)NULL, //идентификатор родительского окна
(HMENU)NULL, //идентификатор меню
(HINSTANCE)hInstance, //идентификатор экземпляра программы
(HINSTANCE)NULL); //отсутствие дополнительных параметров
//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
//Цикл обработки сообщений
//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
LOGFONT lf;
HFONT hFont;
RECT r;
HBRUSH hBrush;
HPEN hPen;
//Цикл обработки сообщений
switch(messg)
<
//сообщение рисования
case WM_PAINT :
hdc=BeginPaint(hWnd, &ps);
//Создаём свой шрифт
strcpy(lf.lfFaceName,»Times New Roman»); //копируем в строку название шрифта
lf.lfHeight=20;
lf.lfItalic=1;
lf.lfStrikeOut=0;
lf.lfUnderline=0;
lf.lfWidth=10;
lf.lfWeight=40;
lf.lfCharSet=DEFAULT_CHARSET; //значение по умолчанию
lf.lfPitchAndFamily=DEFAULT_PITCH; //значения по умолчанию
lf.lfEscapement=0;
hFont = CreateFontIndirect(&lf);
SelectObject(hdc, hFont);
SetTextColor(hdc, RGB(0,0,255));
TextOut(hdc, 80,40, «Красота спасёт мир!!», 20);
//рисуем зелёный эллипс
hBrush=CreateSolidBrush(RGB(10,200,100));
SelectObject(hdc, hBrush);
Ellipse(hdc, 20,100,200,200);
//рисуем закруглённый прямоугольник
hBrush=CreateSolidBrush(RGB(250,200,100));
SelectObject(hdc, hBrush);
hPen=CreatePen(2,2,RGB(0,0,255));
SelectObject(hdc, hPen);
RoundRect(hdc, 20, 250, 250, 350, 15, 15);
ValidateRect(hWnd, NULL);
EndPaint(hWnd, &ps);
break;
//сообщение выхода — разрушение окна
case WM_DESTROY:
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
DeleteObject(hPen);
DeleteObject(hBrush);
break;
default:
return(DefWindowProc(hWnd, messg, wParam, lParam)); //освобождаем очередь приложения от нераспознаных
>
return 0;
>
Правда, красиво? Наконец-то из этой API нам удалось выжать что-то стоящее. Продолжаем обзор.
7. Прямоугольная рамка — как видите, существует немало функций для работы с прямоугольниками:
int FrameRect(HDC hDC, CONST RECT *lprc, HBRUSH hbr);
Применение аналогично предыдущей.
8. Инверсия значения цветов точек в заданной области
BOOL InvertRect(HDC hDC, CONST RECT *lprc);
9. Эллипс
BOOL Ellipse(HDC hdc, int x1, int y1, int x2, int y2);
координаты — это прямоугольник, в который вписывается эллипс
10. Хорда (сегмент эллипса) — параметры аналогичны Arc
BOOL Chord(HDC hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2); Функция соединяет хордой точки начала и конца дуги эллипса и закрашивает выделенный сегмент текущей кистью.
11. Сектор эллипса — аналог pieslice в DOS.
BOOL Pie(HDC hDC, int left, int top, int right, int bottom, int x1, int y1, int x2, int y2);
12. Многоугольник . Есть много функций рисования мноугольников. Мы рассмотрим две. Рисования от вершины к вершине и рисования отрезками:
PolyDraw оперирует вершинами:
POINT poly[8];
BYTE polytype[8];
poly[0].x=375; //координаты первой вершины
poly[0].y=375;
. //и так заполняем координаты всех восьми вершин
poly[7].x=400; //координаты восьмой вершины
poly[7].y =400;
. //другой массив содержит режим рисования
PolyDraw(hdc, poly, polytype, 8); //рисование многоугольника
Функция Polyline рисует набором отрезков:
Polyline(hdc, poly , 4);
КАК вывести график функции?
Всё это были фрагменты графических функций. Теперь рассмотрим настоящий полноценный пример. Это будет простейший график функции.
Как вы сами понимаете, мы не можем обойти столь важную тему. Именно для неё наша следующая программа.
Создайте пустой проект Win API и включите в него следующий текст:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//Процедура рисования линии
BOOL Line(HDC hdc, int x1, int y1, int x2, int y2);
char szProgName[]=»Имя программы»;
int i, xView, yView;
double y;
char Buf[2];
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
<
HWND hWnd;
MSG lpMsg;
WNDCLASS w;
w.lpszClassName=szProgName;
w.hInstance=hInstance;
w.lpfnWndProc=WndProc;
w.hCursor=LoadCursor(NULL, IDC_ARROW);
w.hIcon=0;
w.lpszMenuName=0;
w.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
w.style=CS_HREDRAW|CS_VREDRAW;
w.cbClsExtra=0;
w.cbWndExtra=0;
//Если не удалось зарегистрировать класс окна — выходим
if(!RegisterClass(&w))
return 0;
//Создадим окно в памяти, заполнив аргументы CreateWindow
hWnd=CreateWindow(szProgName,
«График функции»,
WS_OVERLAPPEDWINDOW,
100,
100,
500,
400,
(HWND)NULL,
(HMENU)NULL,
(HINSTANCE)hInstance,
(HINSTANCE)NULL);
//Выводим окно из памяти на экран
ShowWindow(hWnd, nCmdShow);
//Обновим содержимое окна
UpdateWindow(hWnd);
//Цикл обработки сообщений
while(GetMessage(&lpMsg, NULL, 0, 0)) < //Получаем сообщение из очереди
TranslateMessage(&lpMsg); //Преобразует сообщения клавиш в символы
DispatchMessage(&lpMsg); //Передаёт сообщение соответствующей функции окна
>
return(lpMsg.wParam);
>
//Функция окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg,
WPARAM wParam, LPARAM lParam)
<
HDC hdc; //создаём контекст устройства
PAINTSTRUCT ps; //создаём экземпляр структуры графического вывода
HPEN hPen; //создаём перо
//Цикл обработки сообщений
switch(messg)
<
case WM_SIZE:
xView=LOWORD(lParam);
yView=HIWORD(lParam);
//сообщение рисования
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
SetMapMode(hdc, MM_ISOTROPIC); //логические единицы отображаем, как физические
SetWindowExtEx(hdc, 500,500, NULL); //Длина осей
SetViewportExtEx(hdc, xView, -yView, NULL); //Определяем облась вывода
SetViewportOrgEx(hdc, xView/6, yView/2, NULL); //Начало координат
//Рисуем оси координат
Line(hdc,0, 220,0,-220);//ось У
Line(hdc, -100,0,500,0);//ось Х
MoveToEx(hdc, 0,0,NULL); //перемещаемся в начало координат
//Создание красного пера
hPen=CreatePen(1,4,RGB(255,25,0));
SelectObject(hdc, hPen);
//Делаем перо снова чёрным
hPen=CreatePen(1,1,RGB(0,0,0));
SelectObject(hdc, hPen);
//Наносим деления
for(i=-100; i
//сообщение выхода — разрушение окна
case WM_DESTROY:
DeleteObject(hPen); //не забываем уничтожать перья
PostQuitMessage(0); //Посылаем сообщение выхода с кодом 0 — нормальное завершение
break;
default:
return(DefWindowProc(hWnd, messg, wParam, lParam)); //освобождаем очередь приложения от нераспознаных
>
return 0;
>
//Функция рисования линии
BOOL Line(HDC hdc, int x1, int y1, int x2, int y2)
<
MoveToEx(hdc, x1, y1, NULL); //сделать текущими координаты x1, y1
return LineTo(hdc, x2, y2); //нарисовать линию
>
Поскольку в данной программе большое внимание уделяется всяким украшательствам: делениям и надписям, обращу ваше внимание на главное — создавать графики совсем не сложно. И вот как это делается:
Мы знаем, что отсчёт координат задаётся от левого верхнего угла вниз и вправо. Как известно, значения на графике изменяются вверх и вправо. Вряд ли мы сможем объяснить пользователю, почему график «растёт вниз». На счастье, в Windows предусмотрена функция, преобразует координаты в нужном нам направлении.
Первым делом, мы узнаём размер окна. Для этого используется сообщение WM_SIZE. Параметр lParam содержит по этому сообщению размеры экрана. Переменные xView и yView будут содержать эти значения:
case WM_SIZE:
xView=LOWORD(lParam);
yView=HIWORD(lParam);
break;
Затем определим область вывода. Мы хотим, чтобы при увеличении координаты по у график рос вверх, а не вниз.
SetViewportExtEx(hdc, xView, -yView, NULL);
Обратите внимание: yView указан со знаком —. Значит все координаты по у будут расти в обратную сторону — вверх.
Центр графика обычно где-нибудь посередине экрана. Координаты же увеличиваются из левого верхнего угла. Перенесём центр графика:
SetViewportOrgEx(hdc, xView/6, yView/2, NULL);
В точке, равной 1/6 максимального значения по х и 1/2 значения по этого значения по у будет центр.
Можно также задать длину осей — 500 и 500. Для этого применяется следующая функция:
SetWindowExtEx(hdc, 500,500, NULL);
Как вы уже знаете, окно имеет логические координаты и физические. Для того, чтобы логические координаты совпадали с физическими, а также, чтобы единица отложенная по х была равно единице отложенной по у, задаётся режим MM_ISOTROPIC. Его задаёт функция:
Вот, как будет выглядеть эта конструкция вцелом:
SetMapMode(hdc, MM_ISOTROPIC); //логические единицы отображаем, как физические
SetWindowExtEx(hdc, 500,500, NULL); //Длина осей
SetViewportExtEx(hdc, xView, -yView, NULL); //Определяем облась вывода
SetViewportOrgEx(hdc, xView/6, yView/2, NULL); //Начало координат
Дальше надо нарисовать оси. К нашей радости, точка 0, 0 сместилась на середину экрана, в левую его часть. Исходя из этого, рисуем оси, применяя самописную функцию Line:
Line(hdc,0, 220,0,-220);//ось У
Line(hdc, -100,0,500,0);//ось Х
MoveToEx(hdc, 0,0,NULL);
В выводе графика нет ничего примечательного. Переменная i меняется от 0 до 450. Подставляя i в формулу, мы получаем зависимость y от i. Рисуем линию до этой точки. Небольшие отрезки сольются в непрерывную линию.
Иногда, требуется хранить значения физических величин несколько дней. Тогда график выполняется с длиннющей горизонтальной полосой прокрутки. При желании, можно вернуться и посмотреть были ли изменения, и если да, то какие.
Другой выход из положения — вести историю, записывая её в файл, а на экран выводя только показания последних пяти минут.
- Задания:
- Краткое неформальное введение в графику Windows
- Вежневец Владимир 1
- Аннотация
- Содержание
- 1 Цель этого документа
- 2 Устройство графики в Windows
- 2.1 Graphics Device Interface и Device Context
- 2.2 Как рисовать в Device Context?
- 2.3 Как рисовать в окно приложения?
- 2.4 Когда рисовать в окно приложения? WM_PAINT — что это?
- 2.5 Как БЫСТРО рисовать в Device Context?
- 2.6 Как загрузить и вывести на экран изображение?
- 2.7 Как нарисовать что-либо на изображении?
- 3 Надстройки над GDI
- 3.1 MFC надстройка над GDI
- 3.2 VCL надстройка над GDI
- 4 Как (относительно) быстро обрабатывать изображения?
- 4.1 Быстрый способ доступа к пикселям в GDI и MFC
- 4.2 Быстрый способ доступа к пикселям в VCL
- Win32 GDI Рисуем круг?
- Решение
- Другие решения
- 🎬 Видео
Видео:Язык Си - Как рисовать в консольном окне средствами WINAPI.Скачать
Задания:
1. Добавьте в приложения с графиком стрелочки и подписи к осям.
2. Нарисуйте снеговика известными средствами GDI. Напомню, что снеговик состоит из трёх непрозрачных эллипсов грязно-белого цвета, на груди у него должны быть пуговки, в руке — метла, а на голове — ведро.
3. Нарисуйте красивый паровозик, клубы из трубы которого будут выводиться в цикле в виде синих эллипсов
4. Нарисуйте кораблик с жёлтой палубой и красными бортами, используя Polyline или Polydraw. Кораблик покоится на синих волнах, которые нарисованы дугами разной толщины. Попробуйте вывести дуги в цикле.
5. Нарисуйте с помощью одних только линий домик с забором
Видео:Как нарисовать пиксель в окне Windows. SetPixel. GDI. WinAPI.Скачать
Краткое неформальное введение в графику Windows
Вежневец Владимир 1
Видео:СОЗДАЁМ ОКНО - C++ WINAPI ЧАСТЬ #1Скачать
Аннотация
Целью этого документа является ознакомление новичков с тем, как устроена 2D графика в Windows и как с помощью имеющихся средств можно делать простые, но полезные вещи.
К данному тексту прилагаются три программки с исходным кодом под Visual C++ 6.0, Delphi 5 и CBuilder 5, в которых дан «скелет» приложения умеющего загрузить, показать, обработать и сохранить изображение в формате bmp.
Видео:1 2 4 сопряжение окружностейСкачать
Содержание
Видео:01. Основы графики Windows. Изучаем GDI. Pen, Brush, Rectangle.Скачать
1 Цель этого документа
Целью этого документа, как и первого (и частично второго) задания в курсе «Машинная графика» для студентов второго курса ВМиК МГУ является ознакомление новичков с тем, как устроена 2D графика в Windows и как с помощью имеющихся средств можно делать простые, но полезные вещи.
Чего мы коснемся:
- Устройство графики в Windows;
- Как рисовать простые вещи (линии, геометрический фигуры, текст) с помощью функций WinAPI 2 ;
- Как загрузить, отобразить и обработать изображение с помощью функций WinAPI;
- MFC надстройка над WinAPI;
- VCL надстройка над WinAPI;
В качестве приложения к данному тексту выступают три программки с исходным кодом под Visual C++ 6.0 (MFC_GML3), Delphi 5 (DelphiBasis) и CBuilder 5 (SDIApp), в которых дан «скелет» приложения, умеющего загрузить, показать, обработать и сохранить изображение в формате bmp.
Видео:Рисуем линию. LineTo. GDI. WinAPI.Скачать
2 Устройство графики в Windows
2.1 Graphics Device Interface и Device Context
Не стану углубляться в теорию строения Windows и ее графической подсистемы (литературы на эту тему написано вполне достаточно), постараюсь коротко изложить некий минимум знаний, который понадобится при программировании простейшей графики в Windows. При этом я постараюсь также дать понимание что и как устроено (пускай на простом уровне).
Во-первых, в Microsoft Windows существует несколько средств для вывода графической информации, включая DirectDraw, OpenGL, GDI и т.д. Мы рассмотрим GDI (Graphics Device Interface) — подсистему Windows, ответственную за вывод графики и текста на дисплей и принтер. Именно она занимается выводом большинства «окошек», которые и составляют то, что видит пользователь Windows на экране. Она является базовым и, пожалуй, простейшим способом вывода графики в Windows.
С графикой Windows с помощью GDI неразрывно связано понятия контекста устройства (device context). Контекст устройства (DC) — это структура данных, содержащая информацию о параметрах и атрибутах вывода графики на устройство (например, дисплей или принтер). Такая информация, в частности, включает в себя: палитру устройства, определяющую набор доступных цветов; параметры пера для черчения линий; параметры кисти для закраски и заливки; параметры шрифта, использующегося для вывода текста.
В GDI существуют пять типов контекста устройства — связанный с дисплеем (Display DC), принтером (Printer DC), контекст виртуального устройства в памяти (Memory DC), контекст метафайла (Metafile DC) и специальный вид контекста — информационный (Information DC).
Первые четыре типа контекста устройства — display, printer, memory и metafile предоставляют унифицированный интерфейс для вывода графической информации на разнотипные устройства, освобождая приложение (и его разработчика) от необходимости заботится о том, куда именно производится вывод графики. Информационный контекст для вывода графики не используется, он служит исключительно для получения информации о параметрах и поддерживаемых режимах устройства, с которым связан.
В чем отличие первых четырех типов контекста? Это можно понять из их названий — Display DC служит для вывода на экран, Printer DC для печати на принтер или графопостроитель, Memory DC служит для создания растровых изображений в памяти с возможностью быстрого их копирования в другие типы контекстов (и обратно), Metafile DC нужен для вывода графики в метафайл. Метафайл — это хранилище последовательности команд GDI, каждая из которых описывает одну графическую функцию. В отличие от растровых файлов, хранящих графическую информацию непосредственно в виде массива пикселов, метафайл ее хранит в виде последовательности команд, которая создает результирующий рисунок.
2.2 Как рисовать в Device Context?
Для вывода графической информации существует набор функций, которые можно разделить на несколько категорий:
- Методы рисования линий: LineTo, MoveTo, Polyline, Arc, ArcTo, PolyBezier, и др.
- Методы рисования замкнутых фигур: Ellipse, Rectangle, Polygon, Pie, Chord и др.
- Методы вывода текста: TextOut, DrawText и т.д.
- Функции работы с растровым изображением: GetPixel, SetPixel, FloodFill, BitBlt и т.д.
Существует отдельная категория функций работы с DC по переключению режимов и установке параметров вывода графической информации. Часть из них устанавливается напрямую через определенные функции (например, SetBkColor), часть — с помощью специальных графических объектов: перо (pen) — задает режим вывода линий (цвет, толщина, стиль); кисть (brush) — регулирует режим закраски фигур (цвет, стиль); шрифт (font) — задает свойства шрифта, которым выводится текст; палитра (palette) — задает набор используемых в DC цветов; область (region) — используются для задания clipping regions — областей отсечения, вне которых вывод графики блокируется.
Работа с графическими объектами производится с помощью их дескрипторов (handles) — HDC, HPEN, HBRUSH, HFONT и т.д. Создание и удаление объектов производится с помощью соответствующих функций — например, объект pen создается с помощью CreatePen, удаляется с помощью DeleteObject. Режимы, задающиеся через графические объекты, переключаются с помощью создания новых объектов и указания контексту (DC) использовать их для вывода графики. Это делается помощью функции SelectObject:
При выборе нового объекта через SelectObject в качестве возвращаемого значения передается дескриптор объекта, бывшего в использовании в DC раньше. Нужно иметь ввиду, что все создаваемые объекты нужно не забывать удалять их после использования. Более того, сам DC всегда создается с некоторыми объектами по умолчанию и при использовании определенных пользователем объектов через SelectObject нужно в конце работы произвести select объектов, которые были в DC изначально (см. пример выше).
2.3 Как рисовать в окно приложения?
Для того чтобы выводить графику в определенное окно вашего приложения нужно сделать буквально следующее:
Получить дескриптор DC, связанный с окном, в которое вы собираетесь рисовать с помощью функции GetDC(). Нарисовать все, что вы хотите, с помощью функций DC и в конце «освободить» контекст с помощью функции ReleaseDC().
Иным образом производится получение/освобождение дескриптора DC при обработке сообщения WM_PAINT — об этом в следующем разделе.
2.4 Когда рисовать в окно приложения? WM_PAINT — что это?
При выводе графики в Windows есть некоторая тонкость, не всегда очевидная новичкам в программировании под среды с графическим интерфейсом. Казалось бы, если нужно что-то отрисовать в окне — получай его контекст и рисуй. Но не все так просто. Стоит свернуть окно или закрыть его часть другим окном — все, что было нарисовано, пропадет.
Дело в том, что Windows не хранит содержимое клиентской части окна. К клиентской части окна относится ВСЕ, кроме заголовка окна и управляющих элементов (controls): меню, панелей инструментов (toolbar), кнопок и т.д. Приложение само должно позаботиться о том, чтобы отрисовывать свои данные в клиентской области, Windows лишь посылает ему уведомление когда это нужно сделать. Делается это посредством посылки окну сообщения WM_PAINT.
Все необходимые действия по полной перерисовке информации клиентской части окна должны вызываться при обработке события WM_PAINT. Важным понятием при обработке этого сообщения является invalid rectangle. Windows определяет invalid rectangle как наименьшую прямоугольную часть окна, которая была «испорчена» и должна быть перерисована заново. Когда система обнаруживает invalid rectangle в клиентской области окна, она генерирует сообщение WM_PAINT. В ответ на сообщение окно может получить структуру PAINTSTRUCT, которая среди прочего содержит координаты invalid rectangle. Это может пригодиться, если есть желание перерисовывать не все окно, а только ту область, что требуется.
При обработке WM_PAINT должна быть вызвана функция BeginPaint, которая снова делает invalid rectangle `нормальным’. Также BeginPaint возвращает дескриптор DC, который должен быть использован для перерисовки клиентской части окна. Нужно иметь в виду, что при обработке WM_PAINT дескриптор DC окна должен быть получен именно с использованием BeginPaint, а освобожден EndPaint, в то время как во всех других случаях отрисовки нужно использовать другие функции (например, GetDC/ReleaseDC). Если invalid rectangle не делается «нормальным» во время обработки этого события (с помощью BeginPaint или ValidateRect), Windows будет слать WM_PAINT окну постоянно.
Пример обработки WM_PAINT:
2.5 Как БЫСТРО рисовать в Device Context?
С каждым DC, предназначенным для графического вывода, связан графический объект bitmap (растровое изображение), который хранит массив пикселей, выводимых на устройство. Для того, чтобы быстро переместить графические данные с одного контекста на другой, можно не повторять все действия по отрисовке, а просто скопировать данные связанного с контекстом bitmap. Для этого даны специальные функции быстрого копирования пикселей (BitBlt, StretchBlt).
Зачем это может быть нужно? Дело в том, что если вы часто рисуете достаточно сложную изменяющуюся картинку средствами GDI, сами операции рисования начинают занимать заметное для пользователя время и возникает неприятный эффект мерцания изображения — когда часть картинки уже перерисовалась, а часть еще осталась старой. Для того, чтобы избежать подобного эффекта новая картинка может создаваться в виртуальном DC в памяти, и потом быстро переносится на экран функциями копирования bitmap.
2.6 Как загрузить и вывести на экран изображение?
Пользуясь базовыми функциями WinAPI, это к сожалению не так-то просто. Никаких встроенных функций по загрузке изображения из bmp файла не предусмотрено, поэтому требуется самостоятельно писать функцию загрузки. Эта функциональность уже тысячу раз реализована, одна из реализаций предлагается вам в примере, который прилагается к данному тексту.
В принципе, если вы не собираетесь выводить загружаемое растровое изображение на экран (а, скажем, только обрабатывать и сохранять), то его можно хранить в совершенно произвольных собственных структурах данных. Однако, если вы хотите иметь возможность быстро вывести ваше изображение на экран, или рисовать в нем средствами GDI, придется хранить его определенным образом. Потребуется создать графический объект bitmap, соответствующий параметрам файла bmp, и загрузить в него данные из файла (пиксели). Пример, как это сделать, содержится в классе DSimpleBitmap в примере MFC_GML3.
Для того, чтобы уметь быстро выводить загруженное изображение на экран, требуется сделать следующее — с помощью функции SelectObject привязать к созданному заранее memory DC загруженный bitmap (вместо default bitmap, создающегося вместе с контекстом) и затем функцией копирования битов вывести в дисплейный контекст, связанный с вашим окном. Пример:
Не забудьте уничтожить все временные объекты, которые создавались (в данном случае — это memory DC). Не забудьте также перед тем как будете уничтожать memory DC, выбрать в него (через SelectObject) объект bitmap, который был создан вместе с контекстом, в противном случае произойдет утечка ресурсов.
2.7 Как нарисовать что-либо на изображении?
Есть как минимум два способа. Первый — это получить указатель на пиксели растрового изображения (вариант как это сделать см. секцию 4.1) и менять их напрямую. Второй — это рисовать на изображении с помощью функций GDI. Для реализации второго варианта нужно создать DC, связать с ним bitmap, на котором хотите рисовать, и затем использовать стандартные функции вывода графики. Пример:
Имейте в виду, объект bitmap может быть одновременно связан только с одним DC.
Видео:программирование C++: рисование кругаСкачать
3 Надстройки над GDI
Для облегчения программирования под WinAPI было создано некоторое количество объектно-ориентированных надстроек для него. В числе самых распространенных — Microsoft Foundation Class Library (MFC) от Microsoft (используемая в MS Visual Studio) и Visual Components Library (VCL) от Borland (используемая в Delphi и C++ Builder). Обе этих библиотеки уже достаточно пожилые, но тем не менее все еще широко распространенные.
С появлением этих (и других) надстроек, люди крайне редко по-прежнему программируют чисто под WinAPI (что в общем-то понятно).
3.1 MFC надстройка над GDI
Для облегчения работы с функциями и структурами GDI в MFC создан набор классов, являющихся обертками для WinAPI структур и дескрипторов.
К их числу относятся CDC, CPen, CBitmap, CFont, CBrush и т.д. Работа с ними практически идентична работе с дескрипторами этих объектов, но несколько удобнее.
Что значит обертками? Это значит, что CPen внутри себя содержит HPEN (доступный как свойство класса) и просто берет на себя некоторые заботы по его созданию, удалению и работе с ним. Похожим образом организованы все обертки.
CDC — это абстрактный базовый класс, у которого есть несколько реализаций — CPaintDC, CClientDC, CWindowDC, CMetaFileDC, каждая должна использоваться в определенных ситуациях.
Работа с графическими фукнциями GDI с использованием MFC несколько упрощается (сравните с примером в разделе 2.2):
К сожалению, никаких средств для загрузки bmp файлов в CBitmap и для простой отрисовки CBitmap в DC в MFC не предоставлено — приходится пользоваться теми же средствами, что и при работе с WinAPI. Обработка WM_PAINT производится практически идентично, за исключением того, что в MFC существует специальный тип CPaintDC, в конструктор и деструктор которого инкапсулированы (встроены) вызовы BeginPaint/EndPaint. Обработка события выглядит следующим образом:
3.2 VCL надстройка над GDI
Visual Components Library (VCL) от Borland делает гораздо более длинный шаг в сторону упрощения работы с графикой.
В этой библиотеке введен класс TCanvas, также являющийся оберткой для HDC (HDC доступен через свойство Handle), но представляющий более высокоуровневый интерфейс для работы с графикой. Переключение режимов производится путем модификации свойств класса TCanvas — Pen, Font, Brush, TextFlags и т.д., что делает переключение режимов рисования значительно проще и прозрачнее и избавляет разработчика от чехарды с SelectObject/GetCurrentObject/DeleteObject. Операции GetPixel/PutPixel реализованы как доступ к двумерному массиву Pixels (что не делает работу с ними более быстрой).
Canvas связан со всеми компонентами VCL, у которых есть клиентская часть, а также с классом TBitmap. Стандартные компоненты Windows такие как кнопки, списки и т.д. Canvas не имеют, так как их полностью отрисовывает Windows. Рисование на Canvas происходит путем вызова соответствующих функций-членов. Пример (сравните с 2.2, 3.1):
Обработка сообщения WM_PAINT происходит без дополнительной заботы о создании DC особым образом (CPaintDC или BeginPaint), просто нужно работать с Canvas перерисовываемого объекта.
Быстрое копирование из Canvas в Canvas осуществляется путем использования функции CopyRect, аналогичной BitBlt, StretchBits.
Загрузка изображения из файла и отображение на экране с использованием VCL значительно упрощается. Растровые изображения, иконки и метафайлы хранятся в соответствующих классах (TBitmap, TIcon, TMetaFile) — наследниках базового класса изображений TGraphic. Для облегчения работы с этими классами в VCL добавлен класс-контейнер TPicture, который может работать с любым из наследников TGraphic, реализуя функциональность загрузки/сохранения и копирования объекта в буфер обмена (clipboard).
В VCL существует еще один класс, облегчающий вывод графического изображения в окно — TImage. TImage — это компонент, содержащий некоторые свойства и параметры, задающие как именно будет отрисовываться изображение в окне приложения. Само изображение хранится в свойстве Picture класса TImage. Стоит иметь в виду, что при использовании TImage VCL полностью берет на себя обработку сообщения WM_PAINT. То есть все, что нарисовано на Canvas компонента TImage, автоматически отображается на экране, когда это требуется — достаточно нарисовать все что нужно один раз.
Загрузка и отображение растровой картинки с помощью TImage показана в примере SDIApp и DelphiBasis, прилагаемых к данному тексту.
Видео:Язык Си - Как создать и открыть окно с помощью функций WinAPI.Скачать
4 Как (относительно) быстро обрабатывать изображения?
При написании фильтров для изображения требуется способ доступа к отдельным пикселям. Самый простой способ — сделать это с помощью функций GetPixel/SetPixel в WinAPI и MFC и с помощью двумерного массива TCanvas->Pixels в VCL. Однако так поступать не стоит, поскольку такой способ является чрезвычайно медленным.
4.1 Быстрый способ доступа к пикселям в GDI и MFC
При работе с функциями GDI напрямую, наиболее удобным представляется создание объекта bitmap, к пикселям которого можно обращаться напрямую. Делается это с помощью функции CreateDIBSection. Одним из выходных параметров этой функции является указатель на переменную, куда при создании bitmap будет помещен указатель на массив пикселей — ppvBits. Запомнив этот указатель, приложение получает прямой доступ к пикселям изображения. Обычно использующиеся true color изображения с глубиной цвета 24 bit хранят данные попиксельно в виде массива троек `BGR’ (каждый пиксель — три байта).
Адрес пикселя с координатами (x, y) для изображений такого типа рассчитывается следующим образом:
| (1) |
Здесь iBytesPerLine — это длина строки изображения в байтах, которая отнюдь не всегда равна ширине изображения, умноженной на три. Для увеличения производительности работы с изображением адреса начал строк выравниваются по границе процессорного слова (4 байта), поэтому если ширина, умноженная на 3, не кратна четырем, каждая из строк дополняется несколькими дополнительными байтами . Рассчитать длину строки в байтах можно по следующей формуле:
| (2) |
Именно таким образом быстрый доступ к пикселям изображения реализован в классе DSimpleBitmap в примере MFC_GML3.
4.2 Быстрый способ доступа к пикселям в VCL
Для того чтобы получить прямой доступ к указателю на пикселы изображения, хранящегося в TBitmap, нужно использовать свойство ScanLine. Это массив указателей на строки пикселей изображения.
Доступ к пикселю с координатами (x, y) осуществляется следующим образом:
Формат хранящихся в изображении данных задается свойством PixelFormat объекта TBitmap. Для полноцветных изображений (PixelFormat = pf24bit) каждому пикселю соответствует три байта, задающие интенсивности каждого из цветовых каналов — ‘BGR’.
Как подобным образом реализовать фильтрацию изображения, показано в примерах SDIApp и DelphiBasis.
Видео:Как нарисовать надпись в C++ || TextOut C++ || WinApi || VisualStudioСкачать
Win32 GDI Рисуем круг?
Я пытаюсь нарисовать круг, и в настоящее время я использую функцию Ellipse ().
У меня есть начальные координаты мыши — x1 и y1 и конечные координаты x2 и y2. Как видите, я заставляю y2 (temp_shape.bottom) быть = y1 + (x2-x1). Это не работает как задумано. Я знаю, что расчет абсолютно неверен, но есть идеи о том, что правильно?
Видео:БАБУИНЫ НЕ ДАРЯТ ПОДАРКИСкачать
Решение
Если ты хочешь Ellipse() Чтобы нарисовать идеально круглый круг, вам нужно дать ему координаты для идеально квадратной формы, а не прямоугольной формы.
Если предположить, x1,y1 начальные координаты перетаскивания и x2,y2 текущие координаты мыши, затем попробуйте это:
Видео:ОКОННОЕ ПРИЛОЖЕНИЕ С НУЛЯ - ПОЛНЫЙ БАЗОВЫЙ КУРС C++ WINAPIСкачать
Другие решения
Я разработал расчет, который работает лучше. Вставлено ниже для тех, кто хочет того же.
Ваш код излишне сложен с временными DC и обратными буферами, и вы воссоздаете GDI-кисти в каждом WM_PAIT? Но это не главное. Вот вопрос: почему вы это делаете:
Какой фреймворк это поверх? .NET, тогда почему бы вам не использовать встроенную двойную буферизацию?
🎬 Видео
Построение эллипсов | Как нарисовать эллипсСкачать
2 2 3 построение изометрии окружностиСкачать
Сделал 3D в консоли с нуля | трассировка лучейСкачать
Что такое класс. Что такое объект класса. Экземпляр класса это. Класс ООП это. Урок #73Скачать
ЭЛЕМЕНТЫ УПРАВЛЕНИЯ - C++ WINAPI ЧАСТЬ #3Скачать
Язык Си - Как создать кнопки, поле ввода, поле со списком на WinApiСкачать
[1] Уроки по WinAPI?! - Создание окна.Скачать