This article presents the Mandelbrot set, but you can spend countless hours zooming in and recoloring Mandelbrots to your heart's content!
Fractals!
Many years ago I created a simple fractal browser using Borland C++ Builder 4.0 (BCB). Some of you remember those times! Anyway, that tool was retired due to its limitations, and possibly a misunderstanding on my part. (The main program I was working on started crashing, and I couldn't find the problem after an extensive search. Also, BCB crapped out once the program got to a certain complexity.)
One of my dreams was to recreate that browser with my greater knowledge, because - let's face it - fractals are cool! This article is simply an excuse to present a slightly improved, slightly worse version of that program so you can play around for yourself! It only presents the Mandelbrot set (whereas the original also did Lyapunov fractals, which explains the 'worse' comment), but you can spend countless hours zooming in and recoloring Mandelbrotds to your heart's content! Make yourself some cool desktop backgrounds! And when you are tired of that, you can code up some extension DLLs, for other fractal types.
If you are a fan of Mandelbrot patterns, and want to jump right in, just download the executable zip file and extract its contents to anywhere on your hard drive. Double click the .exe and start playing!
1/16/2021
- Added Open and Save ability!
- Added white/black bar in coloring window under color gradient, to show which color values are in use on the current display, to eliminate the guesswork when coloring.
- Added color cycling when
FractalBrowser
window goes out of focus. The best of the '80s! Place the window on a second screen for endless color repetitions as you work! - Made scrollbars colorized, so they can be easily seen compared to the default Microsoft scrollbars that seem to possess infinite fade ability.
- Several bug fixes.
- Increased maximum iterations from 5000 to 50000, for the times you need it! Bring your computer to its knees, just like old times! (Good luck. Even taxing views don't take much more than 10 seconds with today's processors. Not the day-long computation process of old times.)
- Added ability to copy/paste hex values from Windows clipboard.
Downloads
Background
Over the years several fractal programs have appeared on CodeProject. My bookmarks contain Andy Bantly's 'Visualizing Fractals' and Peter Kankowski's 'Generating Fractals with SSE/SSE2' A quick search reveals many more, such as Kenneth Haugland's 'Fractals in theory and practice, and Pierre Leclercq's Mandelbrot in C# and Windows forms.
Maybe I haven't dug enough, but the programs I've seen don't let you zoom at will, and recolor like a gambler hooked on slot machines! dwl::fractalBrowser solves those problems! And allows you to easily save desktop backgrounds.
Design
Since this is ostensibly an article, let's talk about design. All design stems from desires. In my case, I wanted to:
- Look at fractals!
- Look at fractals!
- Look at fractals!
I think you are getting the idea. The point is I wanted to see fractals, not waste screen space with controls and, God forbid, ribbons, or other worthless items. So when you first open dwl::fractalBrowser, this is what you are greeted with:
Press the 'T' key, for 'T'oggle, or right-click on the window and select the 'Toggle buttons' option, and you will have the clean screen my heart desired! Press 'C', and you can get right to coloring!
Double-click in the blend line to add a new point, or single click to select the point closest to the mouse click. Move the sliders which, as you can tell by their colors, are red, green, and blue. Pressing the 'S' key before selecting and moving a bar will 'Saturate' the current color, 'G' makes the color 'Greyscale.'
Blend line points can be moved right and left by selecting and dragging in the blend line. They can also be deleted with the 'Delete' key.
Pretty simple!
Zooming in the main window is accomplished with the old-fashioned mousewheel, as I don't have access to a touch screen. Pressing the keyboard 'Space' key, and clicking in the main window and dragging will pan the view.
That is it, for basic operation! Play around until you get something pleasing, then save it as BMP, JPG, or PNG!
One item to briefly mention is the spotty white line underneath the display of the color gradient, circled in this picture:
The white marks indicate which color values are currently being displayed. In total, there are 1000 possible color values as shown in the edit box, but due to the mathematics of the view range not all of them are being used. If the used values are more spread out, the display can utilize more of the colors you select. So in this case, change the edit box value to about 500, and tweak the colors to your liking:
One last thing to mention is the ability to copy/paste hex values from the Windows clipboard allows you to easily play with complimentary, and other colors. Go to a website like https://www.sessions.edu/color-calculator/, mess with the settings, and copy a value with 'Ctrl-C'. Then select a node in the color window and paste that value into that node. You can also copy values out of dwl::fractalBrowser if you so desire, as 'Ctrl-C' on a node will copy that value into the clipboard so you can paste it into another node, or another graphics program (or even a text editor).
Deeper Details
The alternate title to this article is The Mandelbrot Set: DWinLib Style!. I have not had much time to work on my Windows Wrapper, with time being sucked up learning how the Late Bronze Age Collapse worked into the forgotten history I discovered, as well as other projects. (1/16/2021: I'm happy to say I've improved the wrapper some more, and also completed my historic dive into the thoughts that existed during the era of King David, and earlier!)
But this program was always in the back of my mind. I remembered the BCB version was fun, even though my coloring algorithms really sucked on that edition. There were several other problems with that version: I had hard-coded paths into the program, and may even have relied on Borland DLLs. In effect, all I now have is source code, since I can't get BCB working on Windows 10. And that code relied on the Visual Class Library way of doing things. So I couldn't just plunk it into Visual Studio and take off.
Since I had to rework my main program to eliminate the bug, I decided to get to the bottom of things as well as I could, and set about creating DWinLib
to ensure a bottom-up understanding. (Back then, Visual Studio with MFC cost serious money. And all the MFC example code looked ugly to my eyes, not that that is a reason to keep from learning it.) With that, let us start on the task at hand: creating a near-borderless window.
Of course, the task can be accomplished in almost any framework. It is just a matter of setting the window style, which is accomplished either during construction of the window, or afterwards. In DWinLib
, window construction occurs in the createWindow
method. Here is the appropriate snippet from the MainAppWin::createWindow
code:
bool MainAppWin::createWindow() {
ui::MainMenu * theMenu = new ui::MainMenu(this);
mainMenuC.reset(s_cast<ui::MainMenu*>(theMenu));
MainWin::instantiate(mainMenuC.get());
...
ShowWindow(clientWindowC.hwnd(), SW_HIDE);
LONG style = GetWindowLong(hwndC, GWL_STYLE);
style &= ~WS_CAPTION;
SetWindowLong(hwndC, GWL_STYLE, style);
SetMenu(hwndC, NULL);
InvalidateRect(hwndC, NULL, TRUE);
moveButtons();
updateWindow();
...
I believe MFC passes a CREATESTRUCT
around before window construction, which would eliminate the GetWindowLong / SetWindowLong
steps. But the above works, and is better than my initial solution of doing those steps in the wIdle
processing, along with a static bool
variable to ensure it only happens once.
Being a purist, the little bar at the top of the window annoyed me, but I found that when I eliminated it, the resize handles would not appear when the mouse was at the edge of the window. You will find some commented out code in the MainAppWin::windowProc
that starts to address that, but the work wasn't worth it to me - the little title bar stayed. (I believe in XP there was no such bar when the caption was removed.)
DLLs
In the original BCB version, DLLs had been used for extensibility. One DLL was for the Mandelbrot set, another for Julia, and a third for Lyapunov fractals, and they were written in a way that the main program selected which one to display. There were more DLLs for coloring (all of them used crappy algorithms) and they could also be switched on the fly.
Although my earlier DWinLib
work involved some DLLs, I had never tried window instantiation from inside one. The effort brought two things to light worth knowing, for anyone who might do DLL work without frameworks, or who wants to extend dwl::fractalBrowser for their own purposes.
The main thing to be aware of is the DLL needs to know about DWinLib
, or whatever framework you are using. Previously, DWinLib
had three different global pointers used throughout its base: gDwlGlobals
, gDwlMainWin
, and gDwlApp
. I got tired of passing them individually, and placed the second two into the dwl::Globals
unit. This meant only one pointer for passing, but also meant going through the codebase and changing the places the others were used to gDwlGlobals->dwlApp
and gDwlGlobals->mainWin
(after renaming), instead of directly using them.
If you look through the code, you will find that gDwlGlobal
was made part of the Fractal
structure, which is passed to the DLL. The DLL copies that pointer into its own variable that is named the same. That establishes the necessary link between the main program and the DLL. Not very difficult.
The second thing is more of a general rule: always access things in DLLs through the DLL's exported functions. I have violated this in the current dwl::fractalBrowser codebase, because the main window currently calls into the coloring window's key down and key up processing. In order to take the next step, and add other coloring approaches via DLLs, I will have to undo this, so the calls aren't reliant upon the memory layout of a 'ColorChangeWin
.' (Two different coloring DLLs would probably NOT have ColorChangeWin
s that had exactly the same function names and variables, declared in the same order!)
There is a third thing to be aware of, although it is quite trivial. To change the extension for a DLL, just to go the Project->Properties->Configuration Properties->General section, and change the Target Extension to whatever you want. Then, in the code, make sure to use that extension when calling LoadLibrary
.
In dwl::fractalBrowser, .fll has been used for 'Fractal Link Library', and '.cll' has been used for 'Color Link Library'. That is the first step towards being able to add other DLLs to the programs subdirectory and switching between them as desired.
Projects And Solutions
There was one huge lesson learned in this regard: MAKE SURE EVERY PROJECT IN A SOLUTION USES THE SAME 'DEFINE'S!!!
An entire day was wasted because I had forgotten about removing a DWL_DO_LOGGING
declaration from my precompiled header, and placing it into each project's properties. Of course, I changed it in one project and not the others, and when the program executed, a pointer that had been initialized was no longer showing up as initialized! There was a lot of head scratching on that one! Enough to even post a question here.
So now I'm working towards each project having a third build option besides 'Debug' and 'Release'. I haven't finished implementing it in the attached files, but the 'DebugAndLog' selection will someday automatically take care of setting the DWL_DO_LOGGING
flag. - I find myself using OutputDebugString
much more than I ever used logging, because it is much more powerful and far easier to use, as well as being almost instantaneous. So I now recommend going that route instead of the one in the striken text. Here is a Visual Studio code snippet that makes this much easier:
="1.0"="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[
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
To use, after installing the snippet into the snippet's directory, simply type 'debugout
' and then 'Tab'. wstringstream
s make it relatively easy to print out variables and such without having to remember old printf
codes. For example:
int i=10;
float j = 12.01;
std::wstringstream str;
str << "i = " << i << ", j = " << j << std::endl;
OutputDebugString(str.str().c_str());
If you haven't used multiple projects inside a solution, the attached files may help show the way when you need that functionality. There is a project for DWinLib
itself, the main application, each of the DLLs, and libpng
. Projects came in especially helpful because libpng
uses deprecated functions via Microsoft's standards, and instead of having to throw away all the useful error codes in my main project if libpng
was just thrown into it, I defined _CRT_SECURE_NO_WARNINGS
for only the libpng
project. Yay!
Drawing Bitmaps
It had been quite a while since I blitted objects to the screen. In fact, I'm not certain I have ever drawn a Device Independent Bitmap (DIB) to a screen before, and didn't know that for BitBlt
to work you needed to select the DIB into a Device Context before actually performing BitBlt
. I ended up creating a DC::draw
method to take care of the details:
BOOL draw(swc::Bitmap & bitmap, int x, int y) {
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;
}
Perhaps that knowledge will help you out someday.
Speedy Recoloring
As you will notice, dwl::fractalBrowser is quite speedy at recoloring the bitmap once the fractal has been generated. That is because the bits aren't recomputed via the Mandelbrot equation at the coloring step. Instead, the number of iterations that equation took for each pixel is stored into an int
array. The coloring step simply iterates over those stored values and looks up the corresponding color. It still takes time, as you will see in Debug mode, but not nearly as much as the initial generation.
Helper Classes
If you examine the code in the ColorChangeWin.cpp and .h files, you will see something done right. Viewing the MainAppWin.cpp file, you will see something done not so right.
Creating code for handling the window drudgery becomes tedius. Everything, and the kitchen sink, gets thrown in there, and the header definition starts to become a god-awful mess to look through. (Just peruse MainAppWin.h for confirmation!)
Crafting the ColorChangeWin
, I became a little wiser, and created a MouseHelper
class to remove some of those 'kitchen sink' items from the core of the window processing logic. Doing so made for smaller functions inside the ColorChangeWin code, because it just called into the MouseHelper
routines. Those routines were not bogged down in Window's junk, and it made the overall coding process a much happier and easier one than my previous approach. I highly recommend it!
If you have two different windows that logically need a 'MouseHelper
' class, or named something else, I will, to be wordilly redundant, 'highly recommend' using namespaces. If I had to, I would have put MouseHelper
into a 'ccw' namespace (for 'ColorChangeWin
'), and used another acronym/namespace for the other MouseHelper
.
In an old article, I remember mentioning I very rarely used 'friend
' classes. Now that this use has made itself known, I may have more friend
s in my future!
Acknowledgements
It is hard to believe that JPGs and PNGs are almost twenty years old. It is even harder to believe that finding useful libraries for these items is more than a two-minute job! They should be standard.
Anyway, after spending more than four hours trying to get various packages to work, I am very thankful for the libpng
group for their C PNG wrapper, and Rich Geldreich for his C++ JPG wrapper. Thanks! Neither of them sucked up the time the previous tries did, and they worked (after a couple small adjustments).
To Do
- Add more DLLs for other fractal types, and modify the interface of the main program to be able to switch between them.
Add the ability to save and restore position and color information to the fractals. - Possibly add an 'HSV' color picker to the works. But from what I recall, HSV cannot represent all the colors in the RGB space, and from experience in graphics editing packages, it is far easier to get nuanced colors in RGB than HSV, even though it is more sliders to work.
Add multi-threading, or SSE for faster fractal generation on a per-DLL basis. I'm not in a great hurry to do this, as the current Mandlebrot is pretty darn quick, even at 1080p dimensions, even on my older laptop that isn't so quick. But faster is almost always better! - could still change everything to SSE for another probable time savings, but the multithreading improvement was so great I don't feel a real need to. - Panning could be much improved by shifting pixels appropriately, and only regenerating the pattern for newly revealed areas.
- Add ability to 'box' select area to zoom into.
- Figure out method to 'mark' undo levels, so you can 'Ctrl-Z' to undo the zoom process if you wanted.
Playing around revealed that this iteration of my integer text box really sucks. That must be improved in the near future... - Yay! Fixed! Reduce the flicker of the standard controls! (There is an 'rf' namespace version you can plop in if you wish to see the difference.) - insignificant in this application. - Add secondary slider to control initial color offset, to make editing the full color range easier in order to preview what the inactive animation will look like.
Conclusion
That's it! I hope you get sucked into countless hours of time-wastage in your recoloring endeavors!
History
- 31st Mar 2018: Initial version
- 16th January, 2021: Latest update - see details here