65.9K
CodeProject 正在变化。 阅读更多。
Home

dwl::fractalBrowser

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2018 年 3 月 31 日

CPOL

15分钟阅读

viewsIcon

19020

downloadIcon

548

每个人都喜欢曼德勃罗集!这里有一个浏览器!

分形!

很多年前,我使用 Borland C++ Builder 4.0 (BCB) 创建了一个简单的分形浏览器。你们中的一些人还记得那个时代!总之,那个工具由于其局限性而被淘汰了,可能还有我自己的误解。(我正在处理的主程序开始崩溃,经过 extensive 搜索后我找不到问题。另外,当程序达到一定复杂度时,BCB 也会崩溃。)

我的梦想之一是用我更丰富的知识重塑那个浏览器,因为——让我们面对现实吧——分形太酷了!这篇文章只是一个借口,介绍一个稍微改进、稍微逊色的版本,让你们可以自己玩!它只展示了 Mandelbrot 集(而原始版本也做了 Lyapunov 分形,这解释了“逊色”的说法),但你可以花费无数小时来缩放和重新着色 Mandelbrotds,随心所欲!制作一些酷炫的桌面壁纸!当你厌倦了这些,你可以编写一些扩展 DLL,用于其他分形类型。

如果你是 Mandelbrot 图案的爱好者,并想立即开始,只需下载可执行 zip 文件,并将其内容解压到硬盘上的任何位置。双击 *.exe 即可开始玩!

最新更新

1/16/2021

  • 增加了打开和保存功能!
  • 在着色窗口的颜色渐变下方添加了白色/黑色条,以显示当前显示中有哪些颜色值在使用,从而消除着色时的猜测。
  • FractalBrowser 窗口失去焦点时添加了颜色循环。80 年代的最佳特色!将窗口放在第二个屏幕上,在你工作时享受无尽的颜色重复!
  • 使滚动条着色,以便与默认的 Microsoft 滚动条(它们似乎具有无限的淡出能力)相比更容易看到。
  • 修复了几个 bug。
  • 将最大迭代次数从 5000 增加到 50000,以备不时之需!让你的电脑瘫痪,就像过去一样!(祝你好运。即使是 taxing 的视图,在今天的处理器上也不会花费超过 10 秒。不像过去需要一整天的计算过程。)
  • 增加了从 Windows 剪贴板复制/粘贴十六进制值的功能。

下载次数

背景

多年来,CodeProject 上出现了许多分形程序。我的书签中包含了 Andy Bantly 的《Visualizing Fractals》和 Peter Kankowski 的《Generating Fractals with SSE/SSE2》。快速搜索会发现更多,例如 Kenneth Haugland 的《Fractals in theory and practice》以及 Pierre Leclercq 的 Mandelbrot in C# and Windows forms

也许我没有深入研究,但我看到的程序不允许你随意缩放,并像沉迷于老虎机的赌徒一样重新着色!dwl::fractalBrowser 解决了这些问题!并允许你轻松保存桌面壁纸。

设计

既然这本质上是一篇文章,我们就来谈谈设计。所有设计都源于愿望。在我的例子中,我想要

  • 看看分形!
  • 看看分形!
  • 看看分形!

我想你已经明白了。关键是我想要看到分形,而不是浪费屏幕空间在控件上,更不用说功能区或其他无用的东西了。所以当你第一次打开 dwl::fractalBrowser 时,你会看到这个

按 'T' 键,用于 'T'oggle(切换),或者右键单击窗口并选择 'Toggle buttons'(切换按钮)选项,你就会得到我心中渴望的干净屏幕!按 'C' 键,你就可以开始着色了!

在混合线(blend line)上双击添加新点,或单击选择离鼠标点击最近的点。移动滑块,顾名思义,它们是红色、绿色和蓝色。在选择和移动条之前按 'S' 键会使当前颜色“饱和”,'G' 会使颜色“灰度”。

混合线上的点可以通过选择并拖动来左右移动。也可以用 'Delete' 键删除它们。

非常简单!

主窗口的缩放是通过老式的鼠标滚轮实现的,因为我没有触摸屏。按下键盘上的 'Space' 键,然后在主窗口上单击并拖动即可平移视图。

这就是基本操作!随意玩耍,直到得到令人愉悦的画面,然后将其保存为 BMP、JPG 或 PNG!

有一点需要简要提及,就是颜色渐变显示下方那个斑驳的白线,在图中被圈出了

白色标记表示当前显示的是哪些颜色值。总共有 1000 种可能的颜色值,如编辑框所示,但由于视图范围的数学特性,并非所有值都被使用。如果使用的值分布更广,显示可以利用你选择的更多颜色。所以在这种情况下,将编辑框的值改为大约 500,然后根据喜好调整颜色。

最后要提的一点是,从 Windows 剪贴板复制/粘贴十六进制值的功能,可以让你轻松地玩转互补色和其他颜色。访问像 https://www.sessions.edu/color-calculator/ 这样的网站,调整设置,然后用 'Ctrl-C' 复制一个值。然后选择颜色窗口中的一个节点,并将该值粘贴到该节点中。如果你愿意,也可以从 dwl::fractalBrowser 中复制值,因为在节点上按 'Ctrl-C' 会将该值复制到剪贴板,以便你可以将其粘贴到另一个节点、另一个图形程序(甚至文本编辑器)中。

更深层次的细节

这篇文章的副标题是 *DWinLib 风格的 Mandelbrot 集!*。我没有太多时间投入到 我的 Windows 封装库中,因为时间都被用来学习Late Bronze Age Collapse(青铜时代晚期衰落)如何融入我发现的被遗忘的历史,以及其他项目。(2021/1/16:我很高兴地宣布,我又改进了封装库,并且完成了对公元前大卫王时期及更早时期人们思想的历史性深入研究!)

但这个程序一直在我脑海里。我记得 BCB 版本很有趣,尽管我的着色算法在那一版中真的很糟糕。那个版本还有几个其他问题:我将路径硬编码到了程序中,甚至可能依赖于 Borland 的 DLL。实际上,我现在只有源代码,因为在 Windows 10 上我无法运行 BCB。而且那些代码依赖于 Visual Class Library 的做事方式。所以我不能直接将其放入 Visual Studio 然后开始。

由于我不得不重构 我的主程序 来消除 bug,我决定尽我所能深入研究底层,并着手创建 DWinLib 以确保自下而上的理解。(当时,Visual Studio 和 MFC 的费用很高。而所有的 MFC 示例代码在我看来都很难看,但这并不是不学习它的理由。) 这样,我们就开始着手处理当前的任务:创建一个几乎无边框的窗口。

当然,这个任务几乎可以在任何框架中完成。这只是一个设置窗口样式的问题,这可以通过窗口构造期间或之后完成。在 DWinLib 中,窗口构造发生在 createWindow 方法中。这是 MainAppWin::createWindow 代码中的相关片段

bool MainAppWin::createWindow() {
   //Even though dwl::fractalBrowser doesn't show a main menu, it has one. Rather than
   //strip it from the framework, I left it in the code, and the final app, just hidden.
   ui::MainMenu * theMenu = new ui::MainMenu(this);
   //In the following, MainMenu has a virtual destructor, so everything will be deleted
   //properly.  I also noted in the past that gMainWin must be set before the menu
   //instantiation.  That is done in MainWin's constructor, which is called beforehand.
   mainMenuC.reset(s_cast<ui::MainMenu*>(theMenu));
   MainWin::instantiate(mainMenuC.get());

   ...

   ShowWindow(clientWindowC.hwnd(), SW_HIDE);
   LONG style = GetWindowLong(hwndC, GWL_STYLE);
   //The window style I want:
   style &= ~WS_CAPTION;
   SetWindowLong(hwndC, GWL_STYLE, style);
   SetMenu(hwndC, NULL);
   InvalidateRect(hwndC, NULL, TRUE);
   moveButtons();
   updateWindow();
   ...

我认为 MFC 在窗口构造前会传递一个 CREATESTRUCT,这样就可以省略 GetWindowLong / SetWindowLong 步骤。但上面的代码有效,而且比我最初的解决方案要好,那个解决方案是在 wIdle 处理中执行这些步骤,并使用一个 static bool 变量来确保它只执行一次。

作为一个纯粹主义者,窗口顶部的那个小条让我很不爽,但我发现当我把它去掉时,当鼠标在窗口边缘时,调整大小的句柄就不会出现。你会在 MainAppWin::windowProc 中找到一些注释掉的代码,开始解决这个问题,但对我来说,这项工作不值得——那个小标题栏仍然存在。(我相信在 XP 中,当标题被移除时没有这样的条。)

DLL

在最初的 BCB 版本中,DLL 被用于可扩展性。一个 DLL 用于 Mandelbrot 集,一个用于 Julia,第三个用于 Lyapunov 分形,它们的编写方式使得主程序选择显示哪个。还有更多的 DLL 用于着色(它们都使用了很糟糕的算法),并且可以在运行时切换。

尽管我早期的 DWinLib 工作涉及一些 DLL,但我从未尝试过从 DLL 内部实例化窗口。这项工作暴露了两个值得知道的事情,对于任何可能在没有框架的情况下进行 DLL 工作的人,或者想要扩展 dwl::fractalBrowser 以满足自己目的的人来说。

需要注意的主要一点是,DLL 需要了解 DWinLib 或你正在使用的任何框架。以前,DWinLib 在其基类中使用了三个不同的全局指针:gDwlGlobalsgDwlMainWingDwlApp。我厌倦了单独传递它们,于是将后两个放入了 dwl::Globals 单元。这意味着只有一个指针需要传递,但这也意味着要遍历代码库,并将使用其他指针的地方从直接使用它们改为 gDwlGlobals->dwlAppgDwlGlobals->mainWin(重命名后)。

如果你仔细查看代码,你会发现 gDwlGlobalFractal 结构的一部分,该结构被传递给 DLL。DLL 将该指针复制到自己一个同名的变量中。这就建立了主程序和 DLL 之间的必要链接。并不难。

第二点是一个更普遍的规则:始终通过 DLL 的导出函数来访问 DLL 中的内容。我在当前的 dwl::fractalBrowser 代码库中违反了这一点,因为主窗口目前会调用着色窗口的键盘按下和弹起处理。为了向前迈进,并通过 DLL 添加其他着色方法,我将不得不撤销这一点,这样调用就不会依赖于 'ColorChangeWin' 的内存布局。(两个不同的着色 DLL 可能不会有具有完全相同函数名和变量、以相同顺序声明的 ColorChangeWin!)

还有第三点要注意,虽然它非常琐碎。要更改 DLL 的扩展名,只需转到 **Project**->**Properties**->**Configuration Properties**->**General** 部分,然后将 **Target Extension** 更改为你想要的任何名称。然后,在代码中,确保在使用 LoadLibrary 时使用该扩展名。

dwl::fractalBrowser 中,* .fll 已用于“**Fractal Link Library**”(分形链接库),而 '.cll' 已用于“**Color Link Library**”(颜色链接库)。这是实现将其他 DLL 添加到程序子目录并按需切换它们的第一步。

项目和解决方案

在这方面吸取了一个巨大的教训:确保解决方案中的每个项目都使用相同的“DEFINE”!!!

我浪费了一整天时间,因为我忘记了从预编译头文件中删除 DWL_DO_LOGGING 声明,并将其放入每个项目的属性中。当然,我在一个项目中更改了它,而在其他项目中没有,当程序执行时,一个已经初始化的指针不再显示为已初始化!为此我挠头很久!甚至在这里 发布了一个问题

所以现在我正在努力让每个项目除了 'Debug' 和 'Release' 之外,拥有第三个构建选项。我还没有在附件文件中完成实现,但 'DebugAndLog' 选项将来会自动处理设置 DWL_DO_LOGGING 标志。- 我发现自己比以往任何时候都更频繁地使用 OutputDebugString,因为它功能更强大,使用更方便,而且几乎是即时的。所以我现在推荐走这条路,而不是划掉的文本。这是一个 Visual Studio 代码片段,可以使这个过程更容易

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
   <CodeSnippet Format="1.0.0">
      <Header>
         <Title>DebugOut</Title>
         <Shortcut>debugout</Shortcut>
         <Description>Send something to OutputDebugString</Description>
         <Author>Me, Myself, and I</Author>
         <SnippetTypes>
            <SnippetType>Expansion</SnippetType>
            <SnippetType>SurroundsWith</SnippetType>
         </SnippetTypes>
      </Header>
      <Snippet>
         <Declarations>
            <Literal>
               <ID>expression</ID>
               <ToolTip>Item to output</ToolTip>
               <Default></Default>
            </Literal>
         </Declarations>
         <Code Language="cpp">
            <![CDATA[std::wstringstream str;
            str << "$expression$" << std::endl;
            OutputDebugString(str.str().c_str());$end$
            ]]>
         </Code>
      </Snippet>
   </CodeSnippet>
</CodeSnippets>

要使用,在将代码片段安装到代码片段目录后,只需键入 'debugout' 然后按 'Tab'。wstringstream 可以相对容易地打印出变量等,而无需记住旧的 printf 代码。例如

   int i=10;
   float j = 12.01;
   std::wstringstream str;
   str << "i = " << i << ", j = " << j << std::endl;
   OutputDebugString(str.str().c_str());

如果你没有在解决方案中使用过多个项目,那么附件文件可以在你需要该功能时为你指明方向。有一个 DWinLib 本身的项目,主应用程序,每个 DLL,以及 libpng。项目特别有用,因为 libpng 使用了 Microsoft 标准的已弃用函数,而不是将 libpng 直接放入我的主项目中而不得不丢弃所有有用的错误代码,我为 libpng 项目单独定义了 _CRT_SECURE_NO_WARNINGS。太棒了!

绘制位图

我很久没有往屏幕上“blit”对象了。事实上,我不确定我以前是否画过设备无关位图(DIB)到屏幕上,也不知道 BitBlt 要正常工作,你需要在实际执行 BitBlt 之前将 DIB 选择到设备上下文(Device Context)中。我最终创建了一个 DC::draw 方法来处理这些细节

         BOOL draw(swc::Bitmap & bitmap, int x, int y) {
            //dcC is the object to draw to
            //First way to perform this operation:
            HDC localDC = CreateCompatibleDC(dcC);
            BITMAP info;
            int res = ::GetObject(bitmap(), sizeof(BITMAP), &info);

            HBITMAP oldBitmap = (HBITMAP)SelectObject(localDC, bitmap());
            BOOL ret = bitBlt(x, y, info.bmWidth, info.bmHeight, localDC,
                        0, 0, SrcCopy);
            SelectObject(localDC, oldBitmap);
            DeleteObject(localDC);
            return ret;

            //Second way using dwl:
            //swc::DC localDC(dcC);
            //BITMAP info;
            //int res = GetObject(bitmap(), sizeof(BITMAP), &info);
            //localDC.setBitmap(bitmap(), swc::DeleteAction::DontDelete);
            //return bitBlt(x, y, info.bmWidth, info.bmHeight, localDC(),
            //            0, 0, SrcCopy);
            }

也许这些知识将来会对你有帮助。

快速重新着色

你会注意到,dwl::fractalBrowser 在分形生成后能够快速地重新着色位图。这是因为在着色步骤中,位不会通过 Mandelbrot 方程重新计算。相反,方程为每个像素计算的迭代次数被存储在一个 int 数组中。着色步骤只是遍历这些存储的值并查找相应的颜色。在 Debug 模式下,这仍然需要一些时间,但远不如初始生成的时间长。

辅助类

如果你查看 *ColorChangeWin.cpp* 和 *.h* 文件中的代码,你会发现一些做得对的地方。查看 *MainAppWin.cpp* 文件,你会发现一些做得不对的地方。

编写处理窗口繁琐事务的代码会变得乏味。一切,甚至连厨房水槽,都被塞了进去,而头文件定义开始变得难以浏览。(只需浏览 *MainAppWin.h* 即可确认!)

在创建 ColorChangeWin 时,我变得更明智一些,并创建了一个 MouseHelper 类来移除一些“厨房水槽”式的内容,使其不至于充斥到窗口处理逻辑的核心。这样做使得 ColorChangeWin 代码中的函数更小,因为它只调用 MouseHelper 例程。那些例程没有被 Windows 的垃圾代码所干扰,并且使得整体编码过程比我以前的方法更愉快、更容易。我强烈推荐!

如果你有两个不同的窗口逻辑上都需要一个 'MouseHelper' 类,或者其他名称,我会,为了重复而冗长地,'强烈推荐'使用命名空间。如果必须的话,我会将 MouseHelper 放入一个 'ccw' 命名空间(代表 'ColorChangeWin'),并为另一个 MouseHelper 使用另一个缩写/命名空间。

在一篇旧文章中,我记得提到我很少使用 'friend' 类。既然现在这个用法已经显现出来,我未来可能会有更多的 'friend'!

致谢

很难相信 JPG 和 PNG 已经存在近二十年了。更难相信的是,找到它们的有用库比两分钟的工作还要难!它们应该是标准品。

总之,在花了四个多小时试图让各种软件包工作之后,我非常感谢 libpng 团队的 C PNG 封装库,以及 Rich Geldreich 的 C++ JPG 封装库。谢谢!它们都没有像之前的尝试那样浪费时间,而且它们也能正常工作(在进行了一些小的调整后)。

待办事项

  • 添加更多 DLL 以支持其他分形类型,并修改主程序的接口以能够在它们之间切换。
  • 添加保存和恢复分形的位置和颜色信息的功能。
  • 可能添加一个 'HSV' 颜色选择器。但据我回忆,HSV 无法表示 RGB 空间中的所有颜色,并且根据我在图形编辑软件包中的经验,在 RGB 中获得细致的颜色比在 HSV 中更容易,尽管这意味着更多的滑块需要操作。
  • 按 DLL 的基础添加多线程或 SSE 以加快分形生成速度。我并不急于这样做,因为当前的 Mandelbrot 分形速度相当快,即使是 1080p 分辨率,即使是在我的旧笔记本电脑上,它也不是那么快。但更快总是更好!- 还可以通过 SSE 再次改变一切以节省更多时间,但多线程的改进非常显著,以至于我不需要这样做。
  • 通过适当的像素移动,平移可以得到很大改善,并且只为新暴露的区域重新生成图案。
  • 添加“框选”区域以进行缩放的功能。
  • 找出一种方法来“标记”撤销级别,这样你就可以“Ctrl-Z”来撤销缩放过程,如果你愿意的话。
  • 玩耍时发现,我当前的整数文本框迭代效果很差。这必须在不久的将来得到改进...- 耶!已修复!
  • 减少标准控件的闪烁!(有一个 'rf' 命名空间的版本,如果你想看到区别,可以将其放入。)- 在此应用程序中不显著。
  • 添加第二个滑块来控制初始颜色偏移,以便更容易编辑整个颜色范围,从而预览非活动动画的外观。

结论

就这样!我希望你能沉迷于无休止的、浪费时间地进行重新着色!

历史

© . All rights reserved.