Практика работы в среде визуального программирования
Графика. Работа с растровыми изображениями (фракталы)
Алгебраические фракталы
Создадим программу Fractal для построения алгебраических фракталов и рассмотрим на ее примере работу с растровыми изображениями на основе класса CImage. Алгебраические фракталы строятся на основе алгебраических формул, при этом по алгоритму производится последовательность преобразований исходных данных, и результаты очередного шага зависят от предыдущего. Будем строить фрактальное множество, заданное формулой: zn+1=zZnn+c, где : zn, c - комплексные числа, а - номер текущей итерации, z0=0. Для того чтобы получить изображение (фрактал), надо произвести определенное количество итераций для точки комплексной плоскости.
Алгоритм
- Все точки последовательно перебираются и закрашиваются разными цветами в зависимости от номера итерации при выполнении следующих условий:
- модуль zn больше или равен 2 и достигает заданного предела. Можно установить определенные цвета для закрашивания (в программе 16 цветов). Процесс продолжается до тех пор, пока не будут перебраны все точки.
- Для того чтобы определить цвет каждого пиксела во множестве, необходимо выполнить ряд действий:
- Задать значение мнимой части параметра равным максимальному значению по мнимой оси, а значение действительной части параметра - равной минимальному значению по действительной оси.
- Выбрать текущий столбец пикселов.
- Для каждой строки текущего столбца пикселов увеличить счетчик итераций и проверить, не превышает ли модуль комплексного числа на данной итерации значение 2 и не достигнуто ли предельное количество итераций.
- Если хотя бы одно из этих условий не выполняется, нарисовать точку с текущими координатами, задаваемыми номерами столбца и строки, окрашенную цветом, зависящим от номера текущей итерации.
- Перейти к следующей точке столбца, уменьшив значение мнимой части .
- Перейти к следующему столбцу, увеличив значение действительной части.
Создание приложения
Напишем программу, реализующую данный алгоритм. Создадим SDI приложение без поддержки архитектуры "документ вид" (для этого необходимо снять соответствующий флажок во втором окне мастера MFC Application Wizard). Добавим в главное меню пункт Save для сохранения полученного фрактала. Объявление необходимых переменных в прототипе класса CChildView (заголовочный файл ChildView.h):
public: CImage imgOriginal; //Объект класса CImage CRect m_ClientRect; //Переменная для хранения размеров клиентской области double m_LeftBound; double m_RightBound; double m_TopBound; double m_BottomBound; COLORREF m_ColorTable[16]; //Таблица цветов const unsigned int m_MaxIter; //Константа - максимальное число итераций
Проведем необходимую инициализацию в конструкторе (исходный файл ChildView.cpp ):
CChildView::CChildView(): m_MaxIter(256), m_RightBound(0), m_LeftBound(-1.5), m_TopBound(0.5), m_BottomBound(-0.5) { //Создание таблицы цветов m_ColorTable[0] = RGB(128,0,0); m_ColorTable[1] = RGB(255,0,0); m_ColorTable[2] = RGB(255,128,128); m_ColorTable[3] = RGB(255,128,192); m_ColorTable[4] = RGB(255,0,255); m_ColorTable[5] = RGB(128,0,255); m_ColorTable[6] = RGB(0,0,255); m_ColorTable[7] = RGB(0,0,128); m_ColorTable[8] = RGB(0,128,128); m_ColorTable[9] = RGB(0,255,0); m_ColorTable[10] = RGB(128,255,128); m_ColorTable[11] = RGB(255,255,128); m_ColorTable[12] = RGB(255,255,0); m_ColorTable[13] = RGB(255,128,0); m_ColorTable[14] = RGB(255,128,64); m_ColorTable[15] = RGB(128,64,0); }
Добавим в класс CChildView с помощью мастера обработчик изменения размеров окна OnSize.
void CChildView::OnSize(UINT nType, int cx, int cy) { GetClientRect(&m_ClientRect); }
Добавим в класс CChildView функцию для построения фрактала. В прототип класса (файл CChildView.h ) добавим строку:
public: void BuildFractal();
Определим ее в файле ChildView.cpp Функция BuildFractal:
void CChildView::BuildFractal() { imgOriginal.Destroy(); imgOriginal.Create(m_ClientRect.right,m_ClientRect.bottom,24); double f_Dx = (m_RightBound - m_LeftBound)/m_ClientRect.right; double f_Dy = (m_TopBound - m_BottomBound)/m_ClientRect.bottom; Complex c0(m_LeftBound,m_TopBound); for(int i = 0; i < m_ClientRect.right; i++) { for(int j = 0; j < m_ClientRect.bottom; j++) { Complex z0(0,0); int n_Iter = 0; while(n_Iter < m_MaxIter && z0.Abs() < 4) { z0 = pow(z0,z0) + z0*z0*z0 + c0; n_Iter++; } imgOriginal.SetPixel(i,j,m_ColorTable[n_Iter%16]); c0 -= Complex(0,f_Dy); } c0 = Complex(c0.GetRe(),m_TopBound); c0 += f_Dx; } }
В данной функции используются методы класса CImage: Destroy() - для удаления растрового изображения из объекта, Create(int nWidth, int nHeight, int nBPP) - для создания растрового изображения с размерами nWidth и nHeight и глубиной цвета nBPP, SetPixel(int x, int y, COLORREF color) - для закрашивания одного пикселя изображения. Также используется класс Complex, его полный исходный код приведен в приложении. Чтобы его использовать, нужно добавить в программу с помощью утилиты Solution Explorer файлы Complex.h и Complex.cpp в проект и добавить в файл ChildView.cpp следующую строку:
#include "Complex.h"
Построение и вывод изображения на экран будем производить по двойному клику левой кнопкой мыши. Обработчик OnLButtonDblClick (файл CChildView.cpp):
void CChildView::OnLButtonDblClk(UINT nFlags, CPoint point) { BuildFractal(); Invalidate(); CWnd::OnLButtonDblClk(nFlags, point); }
Обработчик OnPaint (файл CChildView.cpp):
void CChildView::OnPaint() { CPaintDC dc(this); // device context for painting if(!imgOriginal.IsNull()) imgOriginal.StretchBlt(dc,0,0,imgOriginal.GetWidth(),imgOriginal.GetHeight(),SRCCOPY); }
Функция StretchBlt копирует изображение объекта класса CImage в контекст устройства, переданный ей в качестве параметра. Чтобы сохранить полученное изображение, обработаем пункт меню Save. Обработчик OnFileSave:
void CChildView::OnFileSave() { CString strFilter; HRESULT hResult; //Строка с поддерживаемыми расширениями strFilter = "Bitmap image|*.bmp|JPEG image|*.jpg|GIF image|*.gif|PNG image|*.png||"; //Создание стандартного диалога сохранения CFileDialog dlg(FALSE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_EXPLORER,strFilter); hResult = (int)dlg.DoModal(); //Вызов диалога сохранения if (FAILED(hResult)) return; //Создание строки с путем и полным именем файла, в который будет производиться сохранение CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { switch (dlg.m_ofn.nFilterIndex) { case 1 : strExtension = "bmp"; break; case 2 : strExtension = "jpg"; break; case 3 : strExtension = "gif"; break; case 4 : strExtension = "png"; break; default : break; } strFileName = strFileName + '.' + strExtension; } //Сохранение hResult = imgOriginal.Save(strFileName); if (FAILED(hResult)) { CString fmt; fmt.Format("Save image failed:\n%x", hResult); ::AfxMessageBox(fmt); return; } }
Для сохранения изображения используется метод класса CImage: Save, которому необходимо передать строку с путем и именем файла. Для использования класса CImage и математических формул, необходимо добавить в файл stdafx.h строки:
#include <atlimage.h> #include <cmath>
Программа готова. Скомпилируем и запустим. В результате должно получиться следующее изображение: рис. 4.1