Click here to Skip to main content
14,542,667 members

dwl::fractalBrowser

Rate this:
5.00 (11 votes)
Please Sign up or sign in to vote.
5.00 (11 votes)
31 Mar 2018CPOL
Everybody Loves the Mandelbrot Set! Here's a browser for it!

Updated 4/7/18

  • Added multithreading
  • Added ability to copy/paste between blendpoints via 'Ctrl-C' and 'Ctrl-V'
  • Added ability to press 'P' (for 'P'ick) while mouse-clicking on graphic, to select color under mouse for current blend point
  • Made selected blend point stand out more on blend bitmap

Fractals!

Image 1

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 the initial, improved version of that program so you may play around for yourself! It only presents the Mandelbrot set, but you can spend countless hours zooming in and recoloring the picture 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!

Downloads

  • Executable zip file - (360 KB) extract zip to anywhere and just run!
  • Solution zip file - (3.01 MB)
    The 'Programs' root folder was originally directly on my 'D' drive. If placed elsewhere, you will have to modify the 'Includes' files. libpng and other prerequisites are in zip. Tested zip, and found that it gave lib linker error with libpng. Rebuilding libpng solved it.

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:

Image 2

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!

Image 3

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, and you will learn that they are in order: 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!

Image 4

Deeper Details

Image 5

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.

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, the main window construction occurs in the createWindow method. Here is the appropriate snippet from the MainAppWin::createWindow code:

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();
   ...

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 ColorChangeWins 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.

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) {
   //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);
   }

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 tedious. 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 wordily 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 friends 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! Done, 4/7/18
  • Panning could be greatly improved by shifting pixels appropriately, and only regenerating the pattern for newly revealed areas. (Multi-threading change on 4/7/18 makes panning MUCH faster.)
  • 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...
  • Reduce the flicker of the standard controls! (There is an 'rf' namespace version in the 'ReducedFlickControls'' directory you can plop in if you wish to see the difference.)

Conclusion

That's it! I hope you get sucked into countless hours of time-wastage in your recoloring endeavors! Feel free to post links to your favorites in the comments!

Image 6

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David O'Neil
Software Developer www.randommonkeyworks.com
United States United States
I am the author of Laughing at the Devil: One Man’s Religious Discoveries. For those who are “ready to look at the world - religion, science, spirituality - differently,” LATD is the book to turn to.

In about 1994 I began studying and documenting the astronomy of our ancestors. A hint lead to many years of partial understanding, before a profound breakthrough occurred and some old myths finally made sense.

The greatest of my discoveries is the celestial observations behind the biblical tale of Samson, which was created 3,000 years ago. That find casts a profound new light on the roots of Western religion, as well as the foundation of modern science. To learn more, visit my website.

Trained as a mechanical engineer, I learned C++ programming on my own in order to create a MIDI program. I am delighted to say I also succeeded in that goal. Happy coding, everybody!

Comments and Discussions

 
QuestionCool ! Pin
FenderBaba1-Apr-18 23:41
MemberFenderBaba1-Apr-18 23:41 
AnswerRe: Cool ! Pin
David O'Neil2-Apr-18 13:32
professionalDavid O'Neil2-Apr-18 13:32 
GeneralMy vote of 5 Pin
BillWoodruff31-Mar-18 23:30
mveBillWoodruff31-Mar-18 23:30 
GeneralRe: My vote of 5 Pin
David O'Neil1-Apr-18 8:15
professionalDavid O'Neil1-Apr-18 8:15 
GeneralRe: My vote of 5 Pin
BillWoodruff1-Apr-18 9:41
mveBillWoodruff1-Apr-18 9:41 
GeneralRe: My vote of 5 Pin
David O'Neil1-Apr-18 14:15
professionalDavid O'Neil1-Apr-18 14:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 31 Mar 2018

Stats

6.8K views
192 downloads
9 bookmarked