GMSAPR
.pdf171
LONG height, width; double resx, resy;
// Перерасчет размера изображения под разрешение принтера width=pCurPic->GetBMWidth(); height=pCurPic->GetBMHeight();
resx=((double)pCurPic->GetBMInfoPtr()-> bmiHeader.biXPelsPerMeter);
resy=((double)pCurPic->GetBMInfoPtr()-> bmiHeader.biYPelsPerMeter);
//Переводим в dpi resx*=(25.4/1000); resy*=(25.4/1000);
//Приведем к разумным пределам
if(resx<=0 || resx> 3000) resx=72; if(resy<=0 || resy> 3000) resy=72; width=(int)(((double)width)*DXRes/resx+0.5);
height=(int)(((double)height)*DYRes/resy+0.5);
// Выводим изображение, соответственно новым размерам pCurPic->DrawBitmap(pDC, 0, 0, width, height);
};
В методе OnPrint() сначала получаем разрешение печатающего устройства с помощью метода CDC::GetDeviceCaps(). Этот метод возвращает разрешение в точках на дюйм (dots per inch - dpi). Растровая картинка тоже имеет характеристику «разрешение», которая записана в заголовке растра BITMAPINFOHEADER в полях biXPelsPerMeter и biYPelsPerMeter. Названия этих полей говорят о том, что разрешение в них записано в точках на метр. Это не может испугать нас, так как мы знаем, что в дюйме ровно 25,4 мм. Поэтому далее разрешение картинки приводится к единицам измерения «точек на дюйм». К сожалению, далеко не все программы, создающие растровые изображения, заполняют поля biXPelsPerMeter и biYPelsPerMeter значениями. Бывает, что программы пишут в такое поле
172
значение 0, а бывает, что оставляют его и вовсе неинициализированным, и там содержится какое-нибудь случайное значение. Эта ситуация проверяется на следующем этапе. Затем размеры картинки масштабируются пропорционально соотношению разрешений картинки и принтера. И, наконец, используются возможности метода CRaster::OnDraw() по выводу изображения на контекст с масштабированием до заданного размера.
В принципе, в метод OnPrint() можно вставить вызов диалога, в котором пользователь мог бы задать нужные ему размеры отпечатка.
Результат работы программы с новым методом OnPrint() показан на рис. 32. Предварительно, с помощью стандартного диалога, вызываемого командой File | Print Setup, установлена альбомная (Landscape) ориентация листа.
Рис. 32. Предварительный просмотр отпечатка
6.15 Заключение
Да, глава получилась не маленькая, но рассмотрено, конечно, далеко не все, чем богаты теория и практика цифровых изображений. Это и хорошо — есть простор для экспериментов. Надеемся, программа BMViewer может
173
послужить полигоном для испытания ваших идей. Приведенную программную реализацию можно, несомненно, значительно улучшить. В текущей реализации количество фильтров ограничено, они «жестко» прописаны в классе CBMDoc.
Можно было бы придумать схему динамического подключения фильтров к программе. В этом случае фильтры можно реализовывать в динамически загружаемых библиотеках (DLL). Это позволило бы расширять возможности программы уже после ее написания. Такая модель реализована в Adobe Photoshop и многих других программах. Конечно, «динамический» подход требует более серьезного осмысления задачи и планирования архитектуры приложения. Однако, даже та схема, что заложена в программу BMViewer, подразумевает возможность расширения функциональности программы. Так что, желаем успехов!
174
7 Создание приложения с использованием библиотек OpenGL
При программировании сложных, больших приложений целесообразно сконцентрировать основное внимание на задачах проекта и использовать для визуализации готовые графические библиотеки, которые позволяют избавиться от массы рутинной работы. Одна из них — OpenGL, описываемая в обзорной части данного пособия.
Необходимость создания специализированных библиотек для работы с графическими данными была обусловлена не только желанием сократить объем труда, но также и тем фактом, что стандартные средства GDI Windows работают с графикой довольно медленно.
Приводится пример использования OpenGL для вывода на экран трехмерной сцены.
Приложение FirstGL, описываемое в данной главе понадобится для выполнения одной из лабораторных работ.
7.1 Общая схема использования библиотеки OpenGL
Создадим однодокументное MFC-приложение, которое будет выводить на экран движущуюся фигуру на фоне растрового изображения. Фигура будет представлять собой композицию из двух трехмерных объектов. Фигура будет вращаться вокруг своей оси и совершать круговые движения по экрану программы. В качестве заднего плана будем использовать растровую BMPкартинку, загружаемую в программу с помощью уже знакомого нам класса CRaster. Визуализация трехмерной картинки будет происходить с использованием алгоритма "Z-буфера" удаления невидимых линий.
Режим отображения заднего плана будем включать или выключать командой View | Background. В режиме показа заднего плана движение объекта несколько замедляется. Это связано с тем, что алгоритму визуализации прибавляется работы.
175
Для реализации графики с использованием OpenGL программа должна, прежде всего, выполнить начальную инициализацию, которая включает следующие действия.
•Подобрать и установить нужные параметры контекста воспроизведения;
•Создать контекст воспроизведения;
•Сделать созданный контекст воспроизведения активным. Программа может иметь несколько контекстов воспроизведения, но активным
является только один.
После выполнения этих операций уже можно что-нибудь рисовать. В случае, если настройки OpenGL по умолчанию не подходят, их можно изменить с помощью функции glEnable. Можно также настроить параметры сцены, например, параметры освещения.
•Следующее, о чем должна побеспокоиться ваша программа, — это обработка сообщения об изменениях размера вашего окна. В обработчике этого сообщения указывается часть окна, в которой будет располагаться контекст OpenGL. При этом контекст воспроизведения может занимать только часть окна, а остальная его часть может использоваться для размещения элементов управления (кнопки, поля ввода и т. п.) и других целей. Кроме того, требуется указать тип проекции, используемой в контексте отображения: перспективная или параллельная. В перспективной проекции две параллельные прямые сходятся вдалеке. В параллельной же проекции они всегда остаются параллельными.
•Требуется установить точку наблюдения (точку, в которой находится камера или глаз наблюдателя) и точку, куда направлен "взгляд".
•Задать ориентацию системы координат.
176
Итак, создадим заготовку простого однодокументного приложения (можно даже отказаться от панели инструментов и строки состояния). Назовем его FirstGL.
После создания каркаса приложения, подключим к проекту библиотечные файлы OpenGL. Для этого открываем диалоговое окно свойств проекта
(команда Project | Settings) и вкладку Linker. В поле Addition Dependencies
добавим имена файлов opengl32.lib, glu32.lib и glaux.lib (имена файлов разделяются пробелами без запятых). В файл StdAfx.h добавим строчки, подключающие заголовочные файлы:
#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glaux.h>
7.2 Модификация класса облика
В классе CFirstGLView объявим переменную m_hGLRC типа HGLRC
(указатель на контекст воспроизведения OpenGL) и переменную
(указатель на объект CclientDC). В класс CFirstGLView добавим также объявления нескольких методов и переменных, назначение которых рассмотрим далее.
class CFirstGLView : public CView
{
protected: // create from serialization only
CFirstGLView();
DECLARE_DYNCREATE(CFirstGLView)
public:
CFirstGLDoc* GetDocument();
// Данные |
|
|
|
|
CClientDC *m_pDC; |
// Контекст устройства |
рисования |
||
HGLRC |
m_hGLRC; // |
Контекст |
воспроизведения |
OpenGL |
double |
m_dProportion; // Коэффициент пропорции размеров экрана |
|||
BOOL |
m_bViewBackground; |
// Флаг "рисовать фон" |
177
//Операции
// Установка параметров воспроизведения
int |
SetWindowPixelFormat(HDC); |
|
void |
Display(); // Вывод всего изображения |
|
void |
DisplayBackground(); // |
Вывод фона |
//Overrides
// ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CFirstGLView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL
//Implementation public:
virtual ~CFirstGLView(); #ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const; #endif
protected:
//Generated message map functions
protected: //{{AFX_MSG(CFirstGLView)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy();
afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnViewBackground();
afx_msg void OnUpdateViewBackground(CCmdUI* pCmdUI); //}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
#ifndef _DEBUG // debug version in Firstvw.cpp
178
inline CFirstGLDoc* CFirstGLView::GetDocument()
{ return (CFirstGLDoc*)m_pDocument; }
#endif
Метод CFirstGLView::SetWindowPixelFormat() предназначен
для установки параметров воспроизведения OpenGL.
int CFirstGLView::SetWindowPixelFormat(HDC hDC)
{
int GLPixelIndex;
//PIXELFORMATDESCRIPTOR — структура, определяющая
//характеристики контекста воспроизведения. Инициализируем
//структуру значениями для полноцветного RGB-режима
PIXELFORMATDESCRIPTOR pfd = |
|
|
{ |
|
|
sizeof(PIXELFORMATDESCRIPTOR), |
// размер структуры |
|
1, |
|
// номер версии |
PFD_DRAW_TO_WINDOW | |
// вывод в окно или на устройство |
|
PFD_SUPPORT_OPENGL | |
// поддержка OpenGL |
|
PFD_DOUBLEBUFFER, |
// двойная буферизация |
|
PFD_TYPE_RGBA, |
// режим RGB |
|
24, |
// 24-битовая глубина цвета |
|
0, 0, 0, 0, 0, 0, |
// игнорируем установки для битовых |
|
|
// плоскостей и их смещения |
|
0, |
// без |
альфа-буфера |
0, |
// без |
смещения битов |
0, |
// без |
буфера-накопителя |
0, 0, 0, 0, |
// без |
смещения бит в буфере |
32, |
// размер z-буфера |
|
0, |
// без |
буфера-трафарета и |
0, |
// без |
вспомогательного буфера |
PFD_MAIN_PLANE, |
// основная плоскость |
|
0, |
// резервный компонент |
|
0, 0, 0 |
// без |
масок слоев |
}; |
|
|
179
//Находит формат пикселов контекста устройства (монитора),
//наиболее близкий к заданному формату пикселов
//GLPixelIndex — номер поддерживаемого пиксельного формата
GLPixelIndex = ChoosePixelFormat( hDC, &pfd); if(GLPixelIndex==0) // Выбираем индекс формата по умолчанию
{
GLPixelIndex = 1;
// Получаем параметры режима if(DescribePixelFormat(hDC,GLPixelIndex,
sizeof(PIXELFORMATDESCRIPTOR),&pfd)==0) return 0;
}
// Устанавливаем режим
if (SetPixelFormat( hDC, GLPixelIndex, &pfd)==FALSE) return 0;
return 1;
}
Метод SetWindowPixelFormat() вызывается из метода-обработчика сообщения WM_CREATE, добавленного в класс CFirstGLView. В методе
OnCreate() создаются контекст Windows-окна программы и совместимый с ним контекст OpenGL, а также устанавливаются параметры визуализации трехмерной сцены.
int CFirstGLView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1) return -1;
// Создаем контекст клиентской части окна if( (m_pDC = new CClientDC(this))==NULL)
return -1;
// Устанавливаем формат пикселов if(SetWindowPixelFormat(m_pDC->m_hDC)==FALSE)
return -1;
180
// Создаем контекст отображения OpenGL
if( (m_hGLRC = wglCreateContext(m_pDC->m_hDC)) == NULL) return -1;
// Делаем контекст отображения активным if(wglMakeCurrent(m_pDC->m_hDC, m_hGLRC)==FALSE)
return -1;
//Устанавливаем параметры отображения
//Параметры материала
//отражение фонового света
GLfloat mat_ambient[4] = {0.2, 0.2, 0.2, 1.0}; // диффузионное отображение
GLfloat mat_diffuse[4] = {0.8, 0.8, 0.8, 1.0}; // зеркальное отражение
GLfloat mat_specular[4] = {1.0, 1.0, 1.0, 1.0}; // интенсивность зеркального отражения
GLfloat mat_shineness = 50.0;
// Устанавливаем параметры материала glMaterialfv(GL_FRONT,GL_AMBIENT, mat_ambient); glMaterialfv(GL_FRONT,GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT,GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT,GL_SHININESS, &mat_shineness);
// Задаем положение источника света
GLfloat pos[4] = {-100, 100, 100, 0}; glLightfv(GL_LIGHT0, GL_POSITION, pos);
//Активизируем настройки:
//использовать текущий цвет для задания свойств материала
glEnable(GL_COLOR_MATERIAL);
// для вычисления цвета использовать текущие параметры glEnable(GL_LIGHTING);
//учитывать источник света №0 glEnable(GL_LIGHT0);
//проводить тест глубины
glEnable(GL_DEPTH_TEST);
// выводить на экран пикселы с наименьшими z-координатами