
- •Режимы рисования, устанавливаемые вызовом функции cdc::SetRop2()
- •Задание 1. Создание объектов контекста устройства, инструментов рисования и демонстрация режимов отображения
- •Создание графических изображений закраской пикселов
- •Задание 2. Цветовая закраска пикселов при создании узоров
- •Подведение итогов
- •Глава 11. Использование функций рисования
- •Функции рисования графических примитивов
- •Рисование прямых и кривых линий
- •Рисование фигур с замкнутым контуром
- •Дополнительные функции рисования класса cdc
- •Задание 1. Демонстрация графических примитивов
Задание 2. Цветовая закраска пикселов при создании узоров
Программа рисует рекурсивный узор, представляющий множество Мандельброта, заполняя окно представления. Узор перерисовывается при изменении размера окна, удалении перекрывающего окна или выполнении команды меню FileNew. На рис. 10.8 показано окно программы после того, как рекурсивный узор был полностью нарисован.
Рис. 10.8. Результат работы программы Mandel (изображение множества Мандельброта)
Изображенный на рис. 10.8 узор — типичный представитель фрактальных объектов и один из самых известных из них. Его отличительные особенности — размерность множества, лежащая между единицей и двойкой и не являющаяся целым числом, а также самоподобие, выражающееся в том, что любая сколь угодно малая часть множества будет подобна множеству исходного размера.
Основатель направления фрактальной геометрии Б. Мандельброт дал следующее определение фрактала: "Фракталом называется структура, состоящая из частей, которые в каком-то смысле подобны целому". Можно взять любой участок идеального фрактала, увеличить его в любое число раз, и он будет в точности повторять исходный объект или некую его часть. Сам термин произошел от английского слова "fraction", что означает "дробь", "делимый", и был также введен Мандельбротом. Множество Мандельброта — представитель группы фракталов, которые называются алгебраическими. Свое название они получили за то, что их строят, на основе алгебраических формул. При этом производится последовательная серия преобразований над исходными данными по правилу, описываемому этими формулами, и каждый последующий шаг зависит от результатов выполнения предыдущего. Для динамических систем характерно, что, в зависимости от начальных условий, функция, описывающая такую систему, может прийти в одно из нескольких определенных состояний: она может устремиться к бесконечности, сойтись к какому-то определенному числу или числовому диапазону, нулю, например, или ее поведение будет хаотическим.
Множество Мандельброта определяется следующим уравнением:
здесь переменная Z и параметр С — комплексные, n — текущий номер итерации.
Напомним, что каждому комплексному числу С можно поставить в соответствие пару действительных чисел (a, b). В результате комплексное число можно записать в виде суммы C = a + ib,где i — мнимая единица, удовлетворяющая соотношению i2 = ‑1. Действительные числа a = Re(C) и b = Im(C) называются соответственно действительной и мнимой частями комплексного числа С. Сложение и умножение комплексных чисел удовлетворяют таким правилам:
Нам понадобится возводить в квадрат комплексное число. Для квадрата комплексного числа имеем:
Комплексное число удобно изображать точкой или соответствующим радиус-вектором на комплексной плоскости. Абсцисса и ордината точки представляют соответственно действительную и мнимую части числа С.
Чтобы получить картинку, приведенную выше, необходимо производить определенное количество итераций для каждой точки С на комплексной плоскости. Алгоритм такого метода довольно прост: мы последовательно перебираем все точки С, закрашивая их разными цветами в зависимости от номера итерации при выполнении условий: | Zn | 2,0 и n достигает заранее заданного предела. Можно установить определенные цвета закрашивания (в нашем случае используется 6 цветов). Процесс продолжается до тех пор, пока не перебраны все точки плоскости.
Поскольку рисование рекурсивного узора занимает много времени (это в значительной степени зависит от аппаратуры), программа Mandel не рисует полную фигуру с помощью функции CMandelView::OnDraw(). Блокирование обработки сообщений программой Mandel на протяжении всего времени рисования узора запретило бы пользователю выбирать команды меню или других программ. Напомним, что функция обработки сообщений, например, CMandelView::OnDraw(), должна возвращать управление перед обработкой следующего сообщения. Программа Mandel рисует только единственный столбец пикселов внутри узора каждый раз при вызове функции CMandelApp::OnIdle(). После рисования этого столбца функция CMandelApp::OnIdle() возвращает управление, позволяя обработать любое ожидаемое сообщение. Затем функция CMandelApp::OnIdle() получает управление и рисует следующий столбец. Этот процесс повторяется до завершения рисования узора.
Для программной реализации алгоритма вычисления цвета каждого пиксела во множестве Мандельброта необходимо выполнить следующие действия:
задать значение мнимой части параметра С равным максимальному значению по мнимой оси, а значение действительной части параметра С — минимальному значению по действительной оси.
выбрать текущий столбец пикселей.
для каждой строки текущего столбца пикселей увеличить счетчик итераций и проверить условие: не превышает ли модуль комплексного числа на данной итерации значения 4 и не достигнуто ли предельное количество итераций. Предварительно определить квадрат модуля комплексного числа как сумму квадратов его действительной и мнимой частей.
если хотя бы одно из этих условия не выполняется, то нарисовать точку в текущих координатах, задаваемых номерами столбца и строки, и цветом, зависящим от номера текущей итерации
перейти к следующему столбцу, уменьшив значение мнимой части параметра С
и увеличив значение его действительной части.
Для разработки кода программы Mandel по описанному выше алгоритму выполните следующие действия.
Создайте проект Mandel с поддержкой архитектуры Документ/Представление
Для построения множества Мандельброта необходимо инкапсулировать в классе представления переменные, которые используются для генерации графического узора. В диалоговом окне Add Member Variable Wizard в поле Access выберите значение private, в поле Variable type укажите значение int, в поле Variable name введите значение m_Col. Переменная m_Col используется для хранения номера текущего столбца пикселов рекурсивного узора. Выполните аналогичные действия для добавления переменных m_ColMax и m_RowMax типа int, которые сохраняют номера последнего столбца и последней строки пикселов узора и зависят от размеров окна. Переменные m_CRe и m_CIm типа float используются для хранения действительной и мнимой части комплексной переменной (точки в комплексной области). Переменные m_DCRe и m_DCIm типа float определяют приращение действительной и мнимой части комплексной переменной. В результате проделанных действий вы получите объявления переменных-членов, представленных в коде следующим образом:
class CMandelView : public CView { protected: // create from serialization only CMandelView(); DECLARE_DYNCREATE(CMandelView) // Attributes public: CMandelDoc* GetDocument() const; // Overrides public: virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Implementation public: virtual ~CMandelView(); virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; // Generated message map functions protected: DECLARE_MESSAGE_MAP() private: // номер столбца пикселов int m_Col; // номер последнего столбца узора int m_ColMax; // номер последней строки int m_RowMax; // действительная и мнимая часть комплексной переменной float m_CRe; float m_CIm; //приращения действительной и мнимой частей float m_DCRe; float m_DCIm; };
Все добавленные с помощью мастера Add Member Variable Wizard переменные автоматически инициализируются нулевыми значениями в конструкторе класса CMandelView. С целью получения корректного рисунка переменные m_Col, m_CRe и m_CIm будут переопределяться в других функциях. Поэтому вы можете закомментировать соответствующие строки кода:
CMandelView::CMandelView() : //m_Col(0) //определяется в OnDraw() m_ColMax(0) //, m_CRe(0) //определяется в OnDraw() , m_DCRe(0) , m_DCIm(0) , m_RowMax(0) //, m_CIm(0) //переопределяется в DrawCol() { // TODO: add construction code here }
Узор Мандельброта использует некоторые константы, значения которых необходимо определить в программе. Для этого в окне редактора кода файла MandelView.cpp добавьте к имеющимся директивам препроцессора новые для определения следующих макросов.
#define CImMAX 1.2 //с увеличением значения рисунок смещается по //вертикали вниз #define CImMIN -1.2 //с уменьшением значения рисунок смещается по //вертикали вверх #define CReMAX 1.0 //с увеличением значения рисунок смещается влево #define CReMIN -2.0 //с уменьшением значения рисунок смещается //вправо #define MAX_ITERATION 128 //количество итераций
Для получения цветного изображения используйте массив шести основных цветов. Использование массива связано с тем, что на каждой итерации каждая точка будет закрашиваться разным цветом, поэтому необходимо обеспечить доступ к коду цвета в зависимости от номера итерации. Добавьте в файл MandelView.cpp массив цветов, коды которых представлены в шестнадцатеричной системе таким образом:
DWORD ColorTable [6] = {0x0000ff, //красный —RGB(255,0,0) 0x00ff00, //зеленый —RGB(0,255,0) 0xff0000, //синий — RGB(0,0,255) 0x00ffff, //желтый — RGB(255,255,0) 0xffff00, //бирюзовый — RGB(0,255,255) 0xff00ff //сиреневый — RGB(255,0,255) };
Рекурсивный узор полностью заполняет окно представления, следовательно, значения переменных m_ColMax и m_RowMax, используемых для его создания, зависят от размера окна. Они устанавливаются в ответ на сообщение WM_SIZE, передаваемое при первичном создании окна и при каждом изменении его размеров. В файле MandelView.cpp добавьте обработчик OnSize() сообщения WM_SIZE. Для этого в окне Properties класса CMandelView в списке сообщений выберите сообщение WM_SIZE и выполните команду <Add> OnSize. В интерфейс класса CMandelView добавится прототип функции
public: afx_msg void OnSize(UINT nType, int cx, int cy);
В файл MandelView.cpp реализации класса CMandelView добавится макрос ON_WM_SIZE() карты сообщений и шаблон функции.
Добавьте в тело функции CMandelView::OnSize() код, который определяет максимальное количество столбцов и строк, присваивая переменным m_ColMax и m_RowMax значения, равные текущим размерам окна. Кроме этого определяются приращения действительной и мнимой части комплексной переменной, которая используется при генерации узора. Добавленный код показан полужирным шрифтом. Параметры cx и cy задают размеры окна по горизонтали и вертикали.
void CMandelView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here if (cx <=1 || cy <= 1) //проверка деления на нуль return; m_ColMax = cx; m_RowMax = cy; m_DCRe = (CReMAX - CReMIN) / (m_ColMax-1); m_DCIm = (CImMAX - CImMIN) / (m_RowMax-1); }
После вызова функции CMandelView::OnSize() окно представления очищается, а для его перерисовки вызывается функция CMandelView::OnDraw(). Данная функция вызывается также при перерисовке окна представления по какой-либо причине (например, из-за удаления перекрывающего окна). Функция CMandelView::OnDraw() не рисует непосредственно узор. Она переустанавливает столбец в 0 таким образом, что функция рисования узора начинает перерисовывать его с первого столбца. За одну итерацию перерисовывается один столбец. Значение действительной части комплексной переменной устанавливается в ее максимальное значение. Законченное определение функции OnDraw() класса CMandelView выглядит следующим образом.
void CMandelView::OnDraw(CDC* /*pDC*/) { CMandelDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here m_Col = 0; m_CRe = CReMIN; }
Добавьте функцию, которая генерирует один столбец пикселов множества Мандельброта. Назовите эту функцию DrawCol. В окне Add Member Function Wizard (рис. 10.9) в поле Return type выберите тип возвращаемого функцией значения void, поле Parameter type оставьте пустым, в поле Function name введите имя функции DrawCol. Значения остальных полей не меняйте. Нажмите кнопку Finish. В файл MandelView.cpp реализации класса добавиться определение функции.
Рис. 10.9. Окно Add Member Function Wizard при добавлении функции DrawCol() в программу Mandel
class CMandelView : public CView { protected: // create from serialization only CMandelView(); DECLARE_DYNCREATE(CMandelView) private: // номер столбца пикселов int m_Col; // номер последнего столбца узора int m_ColMax; // номер последней строки int m_RowMax; // действительная и мнимая часть комплексной переменной float m_CRe; float m_CIm; //приращения действительной и мнимой частей float m_DCRe; float m_DCIm; public: afx_msg void OnSize(UINT nType, int cx, int cy); void DrawCol(void); };
Добавьте код реализации функции CMandelView::DrawCol(). При каждом вызове эта функция рисует следующий столбец пикселов внутри рекурсивного узора, продвигаясь слева направо. Функция CMandelView::DrawCol() создает объект контекста устройства класса CClientDC, а потом использует уравнение Maндельброта для вычисления цветового кода каждого пиксела в столбце. Чтобы закрасить пиксел, она вызывает функцию CDC::SetPixelV(). Функция CMandelView::DrawCol() возвращает управление сразу, без рисования столбца, если главное окно минимизировано, и CWnd::IsIconic() возвращает значение TRUE. Для получения указателя на объект главного окна, который используется для вызова функции CWnd::IsIconic(), вызывается функция CWnd::GetParentFrame(). Комментарии в коде поясняют реализацию алгоритма.
void CMandelView::DrawCol(void) { CClientDC ClientDC (this); int Iteration; //номер текущей итерации float Im; //значение мнимой части комплексной переменной float ImSqr; //квадрат мнимой части комплексной переменной float ReSqr; //квадрат действительной части комплексной //переменной float Re; //значение действительной части комплексной //переменной int Row; //текущая строка рисунка - номер позиции в //столбце пикселов //если заполнен последний столбец или окно минимизировано, то выход if (m_Col >= m_ColMax || GetParentFrame ()->IsIconic ()) return; m_CIm = CImMAX; //текущее значение мнимой части комплексной //переменной for (Row = 0; Row < m_RowMax; ++Row)//проход по всему столбцу { Re =0.0; Im = 0.0; ReSqr = 0.0; ImSqr = 0.0; Iteration = 0; //пока не закончились все итерации и точка не вышла из круга //радиуса 2 while (Iteration < MAX_ITERATION && ReSqr + ImSqr < 4) { ++Iteration; //перейти к следующей итерации ReSqr = Re * Re;//вычислить составляющие произведения //двух комплексных переменных ImSqr = Im * Im; Im=2*Im*Re+m_CIm; Re = ReSqr - ImSqr + m_CRe; } //отобразить пиксел заданным цветом ClientDC.SetPixelV (m_Col, Row, ColorTable [Iteration % 6]); m_CIm -= m_DCIm; //прирастить мнимую часть комплексной //переменной } m_Col++; //перейти к следующему столбцу m_CRe += m_DCRe; //прирастить действительную часть //комплексной переменной }
Если скомпилировать, построить и запустить программу Mandel, то можно увидеть обычное окно приложения без узора Мандельброта. Это связано с тем, что не генерируются сообщения, в ответ на которые вызывалась бы функция генерации одного столбца рисунка. В нашей программе не предусматривается генерация сообщений в ответ на нажатие кнопок мыши или клавиш клавиатуры, выбора команд меню и пр. действий. Следовательно, программа находится в состоянии ожидания и не занята обработкой каких-нибудь сообщений. Для обработки этого состояния переопределите в классе приложения CMandelApp виртуальную функцию OnIdle() базового класса CWinApp. Для этого в окне Class View выделите класс CMandelApp. В окне Properties щелкните на кнопке Overrides. В списке виртуальных функций выберите функцию OnIdle и выполните команду <Add> OnIdle.
В функцию CMandelApp::OnIdle() добавьте код, который реализует такие действия: вызывает функцию CWinApp::OnIdle() базового класса для выполнения стандартных действий программы, получает указатель на объект представления с помощью функции CFrameWnd::GetActiveView(), вызывает функцию CMandelView::DrawCol() для отображения столбца узора, возвращает значение TRUE для последующего вызова MFC функции CMandelApp::OnIdle(). Добавленный код имеет такой вид:
BOOL CMandelApp::OnIdle(LONG lCount) { //TODO: Add your specialized code here and/or call the base class CWinApp::OnIdle(lCount); CMandelView *PView = (CMandelView*)((CFrameWnd*)m_pMainWnd)->GetActiveView(); PView->DrawCol(); return TRUE; }
В заключение, как обычно, измените заголовок главного окна, добавив в функцию CMandelApp::InitInstance() файла Mandel.cpp такой вызов:
BOOL CMandelApp::InitInstance() { CWinApp::InitInstance(); // Standard initialization // не показан код стандартной инициализации m_pMainWnd->ShowWindow(SW_SHOW); m_pMainWnd->UpdateWindow(); // call DragAcceptFiles only if there's a suffix // In an SDI app, this should occur after ProcessShellCommand m_pMainWnd->SetWindowText("Mandelbrot Demo"); return TRUE; }
Измените значок программы Mandel, следуя описанным в предыдущих занятиях инструкциям. Получите значок, как на рис. 10.10.
Рис. 10.10. Значок программы Mandel
Скомпилируйте, постройте и запустите окончательный вариант программы Mandel. Вы получите узор, показанный на рис. 10.8.
Поэкспериментируйте с программой: изменив сочетания цветов в массиве ColorTable[6], вы получите узор в другой цветовой гамме. Проследите последовательность вызовов функции OnIdle() класса CMandelApp. Для этого можно использовать функцию вывода сообщений в окне сообщений ::AfxMessageBox(). Вызывая функцию задержки выполнения команд программы ::Sleep(), вы можете увидеть создание узора в замедленном темпе.