Introduction ,
Richard T. Stevens的《Turbo Pascal中的分形编程》一书,ISBN 1-55851-106-7,正式向我介绍了分形的奇妙而神秘的世界。我唯一一次见到他们是在卢卡斯电影公司的一款名为“拯救Fractalus”的视频游戏中。直到今天,我仍然深情地记得。这本书附带了一个源盘,在1994年价值20美元,里面有源代码。
救援在Fractalus
多亏了Commodore 64的现代仿真器,我今天还能在Fractalus上玩救援游戏,这让我想起了去年。
当时,PCX格式是图像的主流格式。当我翻开这本书开始阅读的时候,一个全新的世界为我的个人调查打开了。这本书对当时物理学、哲学和计算机系统的流行趋势给出了一个简洁的历史视角,这些学科为了研究混沌理论而聚在一起,争吵不休。
Fractals
然而,在第19页,分形这个词被定义了。分形-描述所有的曲线,其Hausdorff-Besicovitch维数大于欧几里得维数。引言接着用一个示例来解释这个概念。为了掌握这个概念,你可以在一张纸上画一条线,然后把它缠绕在纸上,永远不要画线,直到你填满了整张纸。欧几里德几何学说,这仍然是一条线,但我们的视觉和直觉告诉我们,这是更多,它是二维的
佛
像曼德尔布罗特龙分形
软件Implementation
所提供的软件和源代码同时生成Mandelbrot和Julia分形类型。茱莉亚分形类型是生成经典曼德尔布洛特集的方程的另一种表达式。“曼德尔布罗特集形成了所有可能的茱莉亚集的一种映射。”史蒂文斯- 305。如果您查看感兴趣的Julia设置点,并将曼德尔布罗特放大到这些坐标中,那么在足够大的放大率下,Julia设置的图像将开始浮现。该软件允许您不仅可视化的分形,但动画的颜色调色板和可选的记录动画作为一个AVI文件。好奇的请参阅源代码中的AVI.h和AVI.cpp。有源代码,包装了一个DIBSECTION的创建和访问,这是设备独立的位图。AVIs记录最好在低分辨率。一个320x200分辨率分形将记录好。
这本书开始对方程组作更实际的分析。分形具有自相似性的性质。这种自相似性可以通过几何和几何替换来理解。创建自相似性使用两个部分,生成器和启动器。几何形状变成了发电机。现在我们用另一组具有精确长度的线段替换发电机的每一条线段。这些线段称为启动器。可以是其他几何形状。这个过程被重复了无数次。这就是Hausdorff-Besicovitch的定义起作用的地方。有N个线段,每个线段的长度为r,其中r是被替换线段的一个分数…由此可知,所得分形曲线的Hausdorff-Besicovitch维数D为:D = log N / log (1/r)”——史蒂文斯第28-29页。
![Image 7](/KB/recipes/353651/FieryWater.png)
Mandelbrot的公式是Zn+1 = Zn在此公式中,当n趋近于无穷时,Z的值要么趋向于无穷大,要么保持在X的轨道内。4. Z和C是复数X和Y由这个方程的实部和虚部导出。值保持在轨道上的分在一组。有趣的是,它是可视化的值中不设置是最常见的公认了曼德尔勃特集合,当它实际上not-Mandelbrot集。转述了曼德尔勃特集合从维基百科文章。
这个程序使用MFC类来构建程序的基本结构。本系统采用MDI体系结构。这允许方程的每个可视化存在于具有自己视图的文档中。该程序还允许选择视图的矩形区域来放大、放置一些文本或将图像放置在分形上,并允许不同级别的透明度与分形融合。当光标变成一只手的时候,装饰性的文本可以被编辑并在屏幕上移动。当分形被渲染时,背景图像可以被合并到分形中。有两种选择:与基底混合,一个不移动到无穷大的坐标,或者在“分形”中。放大到Julia集合是有一些限制的。只要矩形区域包含朱莉亚焦点,就可以实现缩放。在Julia分形的上下文敏感菜单中也有2倍变焦。此外,每个可视化都可以保存为可以重新加载的文档,或者保存为图像文件格式的图像。该程序不会限制你的显示器分辨率。您可以创建一个高达28,800 x 16,200像素的图像。您可以在文档周围滚动以查看此类图像的详细信息。您还可以通过缩小来查看完整的图像来获得“大图”,因为它适合文档的视图。坐标在状态栏中得到回显,您可以从任何曼德尔布罗特分形中从当前光标位置生成一个Julia集。右键单击曼德尔布罗特分形并选择Julia Set.
有许多缩放选项。你可以直接缩放到一个拖出的矩形区域。你可以通过在放大的同时给出跳跃的次数来飞到那个区域。你也可以做小变焦或连续小变焦。一个小的变焦会以最小的可能增量放大。一个连续的小变焦将不断变焦,这种方式让你飞到分形。
图形-小变焦和连续小变焦
![Image 12](/KB/recipes/353651/ContextMenu.png)
![Image 13](/KB/recipes/353651/EditText.png)
![Image 14](/KB/recipes/353651/Text_Rect.png)
![Image 15](https://www.codeproject.com/KB/recipes/353651/Picture_Box_1.png)
![Image 16](https://www.codeproject.com/KB/recipes/353651/Picture_Box_2.png)
![Image 17](https://www.codeproject.com/KB/recipes/353651/Picture_Box_3-r-700.png)
![Image 18](/KB/recipes/353651/Text_Editor.png)
![Image 19](/KB/recipes/353651/Text.png)
有着色选项,允许分配和编辑颜色之前或之后的分形已经呈现。在新的分形对话框中,通过点击编辑颜色按钮和双击颜色来完成颜色的编辑。渲染后的颜色编辑是通过右键点击渲染的分形并从上下文菜单中选择编辑颜色来完成的。这个编辑过的颜色修改了创建分形对话框的面板。颜色的基本应用是使用逃逸轨道计数对颜色数进行模数,以表示从0到分形中所需的颜色数之间的值。它被用作颜色的红、绿、蓝值。结果是黑白渐变。此外,还可以应用每个颜色通道的值的偏移量,使图像具有良好的颜色可视化效果。屏幕渐变条会显示分形中出现的颜色。随机按钮为每个通道生成随机缩放。对话框还允许选择六种颜色来表示渐变中的一组颜色。当“渐变”复选框被选中时,“随机”按钮将创建一个随机的渐变并添加可选的缩放。轨道可以根据发生的情况涂上颜色。按较小的轨道排序,将最少出现的轨道按顺序分配给最多出现的轨道。按较大的轨道排序使调色板从最常见的轨道到最不常见的轨道。你可以在分形被渲染后编辑颜色来改变它。这种颜色将与分形文档一起保存,并在下次加载文档时重新加载。您可以通过选中红色、绿色和蓝色复选框来限制出现的颜色通道。基色,或最后一种颜色,总是代表一个集合的解的值。轨道永远不会脱离轨道。你可以通过检查选项和选择颜色来定制这个基本颜色。此外,还可以选择一个背景图像,在底色和分形颜色所在的位置进行混合。使用此选项可以实现良好的效果。例如在水或天空上渲染分形。不透明度以从0到100的百分比输入,其中0完全不混合,100只显示图像。介于两者之间的值将导致图像和分形部分合并。
角Decomposition
有着色选项允许使用角分解着色。角分解将函数的X和Y值转换为一个角度。然后这个角度被缩放到迭代次数,这决定了颜色计数,这个值被用作颜色。一些非常有趣的结果可以使用正常着色和角分解的组合生成。要进行二元分解,请选择一个2色调色板。尝试一种2色、16次迭代、二进制分解的分形。
![Image 21](/KB/recipes/353651/NewFractal.png)
图形-具有背景图像的分形
![Image 23](/KB/recipes/353651/EditColors.png)
分形的细节在很大程度上由算法的迭代次数控制。要检测出一个点不在解决方案中所需要的迭代次数被称为逃逸轨道。逃逸轨道测试的上界越大,分形的细节就越精细。在这个系统中,当你放大到坐标,更多的迭代可以产生更多的细节。因此,如果你被放大到一个小的坐标空间,更少的迭代将产生更少的细节。有时,在迭代和坐标之间找到正确的平衡是一门艺术,并产生非常有趣的图片。请看下面两幅图。创建它们的分形文档与下载的Exe压缩包绑定在一起。对于茱莉亚分形,最好的图像通常是生成较少的迭代。颜色的数量将迭代划分为相似颜色的组。当颜色的数量与迭代的数量相同时,每个迭代都有自己的颜色。
![Trap](https://www.codeproject.com/KB/recipes/353651/Trap-r-700.png)
该系统允许您添加一个阶梯效果的分形,以及平滑任何分形使用菜单选项。平滑是一个低通滤波器,它使用一个像素周围的3×3区域的平均颜色来生成新的像素颜色。阶梯增强了颜色之间的区域,给3D效果。在新的分形对话框中,它可以作为一个从0到10的值输入。0等于没有被使用。
基本的工作部分是一个主菜单,允许您创建、保存、打印和加载文档。如果您想模拟一个德克萨斯州大小的屏幕,它可以让您通过view菜单缩小并查看大图。在每个文档中,可以按下鼠标左键并在要缩放的区域周围绘制矩形。这些是系统的基本工作部分,现在让我们看一些代码!
// Class for calculating the mandelbrot set class CFractalBase { public: CFractalBase(CFractalParm FractalParm); virtual ~CFractalBase(); virtual void operator() (int RowBeg,int RowEnd); protected: virtual void Apply(int Iteration,int Column,int Row); protected: CFractalParm m_FractalParm; };
// Base derived class for holding the canvas (DIB + colors) class CFractalCanvas : public CFractalBase { public: CFractalCanvas(CFractalParm FractalParm,CDIBFrame * pBaseDIB,CDIBFrame * pFractalDIB,CDIBFrame * pDisplayDIB); virtual ~CFractalCanvas(); std::vector<CIteration> GetIterations(); protected: virtual void Apply(int Iteration,int Column,int Row); protected: CDIBFrame * m_pBaseDIB; CDIBFrame * m_pFractalDIB; CDIBFrame * m_pDisplayDIB; std::vector<std::vector<BYTE> > m_RGB; int m_nMaxIterations,m_nMaxCalc; bool m_bModulo; std::vector<CIteration> m_Iterations; };
// Class for rendering the mandelbrot fractal without angular decomposition (main case) class CRenderMandelbrotFractal : public CFractalCanvas { public: CRenderMandelbrotFractal(CFractalParm FractalParm,CDIBFrame * pBaseDIB,CDIBFrame * pFractalDIB,CDIBFrame * pDisplayDIB); virtual ~CRenderMandelbrotFractal(); virtual void operator() (int RowBeg,int RowEnd); };
每个分形类型在上述方案推导。呈现类从canvas类派生,函数操作符被实现。呈现类对方法是通用的,Apply()方法有一个通用的实现。
多线程
由于工作的主要主体围绕着多个线程,并且在MFC MDI体系结构以及曼德尔布罗特集的数学和方程中已经存在了很多内容,因此我将只关注代码的这个方面。希望读者能够在执行期间跟踪代码,设置断点,这样他们就可以“顺应流”了。有一个类包装了系统的多线程方面。这是CDriveMultiThreadedFractal类。
这个类是一个CWinThread派生类,它驱动渲染集合中的一组点的工作。它推动了实际的工作。线程驱动程序用一个CFractalBase类初始化,这个类是多态系统的基类,它可以很容易地添加新的分形类型。(将其连接到UI需要做更多的工作,但这也不是太难)系统确定有多少执行线程可用来完成工作,然后根据行分区对工作进行分区。这遵循了并行for循环的设计模式。这种多线程模式对于基于数学的问题(其中工作可以拆分和组合)具有令人惊讶的效果。为了调试目的,或者只是为了更容易地监视调试器中的流,系统被简化为一个工作线程。在版本构建中,系统可以扩展到最大的线程数。
工作被分配给执行线程。对于系统中的每个CPU,将创建两个工作线程。O/S管理哪个CPU获得执行线程。工作是根据要渲染的图像的高度进行划分的。对于单核系统,将有两个线程执行。每个线程获得图像的1/2。对于双核系统,每个线程获得1/4的映像。这就是多线程设计的好处。代码重用并发发生。
图7 -委派工作的驱动程序类
![](https://www.codeproject.com/images/arrow-up-16.png)
// Driver thread that carries out the function class CDriveMultiThreadedFractal : public CWinThread { DECLARE_DYNCREATE(CDriveMultiThreadedFractal) private: CDriveMultiThreadedFractal() : m_phHandle(NULL) {}; public: CDriveMultiThreadedFractal(HANDLE * phHandle,CFractalBase * pFractal); virtual ~CDriveMultiThreadedFractal(); virtual BOOL InitInstance(); virtual BOOL PumpMessage(); virtual int ExitInstance(); protected: HANDLE m_hPump; bool m_bPumpMessage; HANDLE * m_phHandle; CFractalBase * m_pFractal; public: CFractalBase * GetFractal() {return m_pFractal;} protected: DECLARE_MESSAGE_MAP() protected: afx_msg void OnDoWork(WPARAM wParam,LPARAM lParam); afx_msg void OnEndThread(WPARAM wParam,LPARAM lParam); };
当我第一次开始使用这些类型的线程时,我偶尔会遇到这样的问题:消息不会被分派,worker函数不会被调用。我了解到的是,线程的消息泵没有启动,我发布到线程的消息不能被分派。为了解决这个问题,我推翻了泵消息,并让它信号时,它准备好了。构造函数在退出之前等待信号。这意味着一旦构造函数在定义了thread对象的实例之后返回,它就可以立即将消息发送到它的消息泵。这是处理关注点分离的好方法。线程代表工作但并不做任何事情但等待工作要做只就可以将这个类与类的工作但关注点分离将会丢失,代码将变得难以维护和难以建立未来的设计理念。要使用这个对象,我们定义自定义线程消息,如WM_DOWORK,然后将这些消息发布到线程。参见图7中的代码。每个处理自定义消息的函数都使用相同的方式声明:
afx_msg void OnFunction(WPARAM wParam,LPARAM lParam);
线程的消息映射将这些消息映射到它感兴趣的功能。可以使用WPARAM和LPARAM变量传递补充信息。
![](https://www.codeproject.com/images/arrow-up-16.png)
// These functions are called by PostThreadMessage BEGIN_MESSAGE_MAP(CDriveMultiThreadedFractal, CWinThread) ON_THREAD_MESSAGE(WM_DOWORK,&CDriveMultiThreadedFractal::OnDoWork) ON_THREAD_MESSAGE(WM_ENDTHREAD,&CDriveMultiThreadedFractal::OnEndThread) END_MESSAGE_MAP() void CDriveMultiThreadedFractal::OnDoWork(WPARAM wParam,LPARAM lParam) { // Do the work int RowBeg = (int)wParam; int RowEnd = (int)lParam; try { // Carry out the work for this range m_pFractal->operator()(RowBeg,RowEnd); } catch (...) { } // Signal completion HANDLE & hHandle = *m_phHandle; SetEvent(hHandle); } void CDriveMultiThreadedFractal::OnEndThread(WPARAM wParam,LPARAM lParam) { // Delete the work object if (m_pFractal) delete m_pFractal; // End the thread PostQuitMessage(0); }
为了完成这项工作,必须存在能容纳工人的工厂。我们预先知道我们有多少工作线程,所以我们创建了一个可以有这么多工作线程的工厂。
// Construct the factory that does the work CDriveMultiThreadedFractal ** ppDriveMultiThreadedFractal = new CDriveMultiThreadedFractal * [nTotalThreads];
现在我们想要创造我们的工人。我们的工作者可以是我们所支持的任何分形类型,因为我们使用的是表示如何执行任务的公共基类和接口。
for (int iHandle = 0;iHandle < nTotalThreads;++iHandle) { // Construct the objects that do the work CFractalBase * pMultiThreadedFractal = NULL; if () pMultiThreadedFractal = new Fractal1(); else if () pMultiThreadedFractal = new Fractal2(); else if () pMultiThreadedFractal = new Fractal3(); etc...
现在我们在工厂中构建我们的工作地点,并将我们的工人安置在他们的工作区中,以使他们准备好开始他们的任务。
// Construct the Fractal thread driver CDriveMultiThreadedFractal * & pDriveMultiThreadedFractal = ppDriveMultiThreadedFractal[iHandle]; pDriveMultiThreadedFractal = new CDriveMultiThreadedFractal(&arrReadHandle[iHandle],pMultiThreadedFractal);
现在我们把工作分派给我们的工人,并等待他们完成他们的任务
/ Process the number of segments that make up the Fractal int MRPT = MR / nTotalThreads + 1; // Process the number of threads of work for (int iThread = 0;iThread < nTotalThreads;iThread++) { // Set the range of rows to process int nBegRow = iThread * MRPT + 1; int nEndRow = min(nBegRow + MRPT - 1,MR); // Perform the work for this range CDriveMultiThreadedFractal * pDriveMultiThreadedFractal = ppDriveMultiThreadedFractal[iThread]; pDriveMultiThreadedFractal->PostThreadMessage(WM_DOWORK,nBegRow,nEndRow); } // Wait for all the work to complete for this set of ranges, the driver signals these events WaitForMultipleObjects(nTotalThreads,&arrReadHandle[0],TRUE,INFINITE);
消息映射处理两个重要的线程消息。它处理工作消息和退出消息。消息本身的声明如下:
#define WM_DOWORK (WM_APP + 100) #define WM_ENDTHREAD (WM_APP + 101)
将它们声明为WM_APP到WM_APP + 0xBFFF之外的值是很重要的。
其余的代码基于一个通用实现,该实现将工作单元分派给它所持有的对象。这种设计模式的伟大之处在于,像这样的通用多线程驱动程序类可以被调整为将工作委托给实现函数操作符的任何对象。对于我的特殊目的,函数运算符作用于表示我想计算的部分方程的值范围。一个比我更好的程序员可以更聪明地对这个驱动类进行模板化,以保存一个T类型的任意对象并将其用作工作工厂设计模式。我使用这种设计模式的其他方式是在图像数据的多线程处理。CColorOp类再次演示了此模式。
更新
- 09/20/17 -修正了一个轨道捕获的错误
- 06/25/16 -增加小变焦和连续小变焦。对默认图像嵌入的文件格式进行了小的更改,以便与可能没有添加到文档中的图像的人共享文档。
- 06/18/16 -所有分形类型包含源代码。为支持透明颜色的图像添加了图片框。图片和文本框调整大小。使用从CDocManager派生的新类打开多个文档。Fly into工作起来更加流畅,并且绑定到消息泵中。错误修复和文件格式更改。
- 10/24/13 -添加轨道捕获(拾取秸秆)到分形创建对话框
- 5/9/13 -增加了一个PayPal捐赠按钮
- 5/5/13 - AX2+/- BY2 <方程散度检验的精细控制救助半径。 模式1是AX2 + BY2 <r模式2是AX2 - BY2 <R又是;模式3是BY2 - AX2 <R又是;模式4是|AX2 - by2| <R。
- 5/3/13 -全彩色编辑之前和/或之后的分形已呈现。
- 4/19/13 -提高佛陀的性能
- 4/18/13 -增加佛分形和反佛分形。佛像布洛特高度为4096,颜色和迭代数为8192或更多,看起来非常棒。警告,渲染会花费很多时间!
- 4/14/13 -更新的源代码,见下面
- 4/13/13 -性能提升
- 4/8/13 -增加了Mandelbrot Phoenix版本2分形。
- 4/7/13 -添加了茱莉亚凤凰分形。茱莉亚分形对话框允许手动编辑边界坐标,这允许你手动缩放到有趣的坐标。
- 4/6/13 -添加茱莉亚龙分形
- 4/6/13 - angular分解的调色板选择错误修正,当基本颜色被固定为在渲染过程中不应该出现在常规调色板上的颜色
- 4/5/13 -基于角分解的新方案颜色,看起来更自然,数学上更讨人喜欢
- 4/4/13 -第一个二进制只更新系统与龙和凤凰曼德尔布罗特类似的曲线。飞在放大时,在现有的文档。更新捆绑文件,包括4条有趣的龙曲线。参见龙。frc,龙二。frc,龙三。frc和龙四。frc。注意——这些与现有的源代码不兼容。
- 4/14/13更新了源代码到增强版,但只使用了曼德布洛特和茱莉亚。其他类型仅在二进制版本中可用。源代码包含着色错误修复和增强的类架构,以优化呈现。
- 4/4/13 -放大现有文档的错误修正。最后一次源代码更新。
- 4/3/13 -算法改变的计算和应用的颜色。
- 4/1/13 -编辑颜色和文本。分离颜色的数量和迭代的数量。
- 11/22/12 -保存彩色动画为AVI文件
- 11/20/12 -改变楼梯的颜色和更快,更流畅的颜色动画
- 11/12/12 -文本现在可以绘制没有背景颜色
- 11/11/12 -添加装饰文本到分形
- 11/4/12 -修复了保存和加载带有嵌入图像的文档的错误
- 11/3/12—改进的混合,使基础颜色和分形颜色可以在不同的不透明度级别上与图像选择性地混合(阿尔法混合)
- 6/23/2012 -螺纹稳定性。我发现并不是所有CPU都喜欢执行线程清理的代码,所以在WM_ENDTHREAD消息被发布到线程消息泵后,对逻辑有一些小的调整。
- 4/15/2012 - 3D效果和图像平滑
- 4/12/2012 -基于角度分解的着色
- 4/10/2012 -更多的动画选项。
- 2012年4月8日-增加了动画调色板的能力。查看彩色的低分辨率视频。mp4。
- 2012年4月7日-添加了茱莉亚分形类型,并能够从曼德尔布罗特上的一个点呈现它。还增加了设置背景图像以显示在基础颜色的能力。
本文转载于:http://www.diyabc.com/frontweb/news14803.html