25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Ads   Donate   Humor

Click to view full size: Mandelbrot set area. Click to view full size.

Winbrot.cpp (Mandelbrot Set)

A full-featured Mandelbrot set generator and zoomer with color cycling, palette manipulation, other effects, and Wow!

Two features set this program apart from many other Mandelbrot calculators:

  1. Auto-run mode, in which it continuously generates at timed intervals pictures of random regions at random magnifications. You don't have to hunt around and zoom in manually (but you can if you want).
  2. The random designs are pre-screened so only interesting ones are displayed. Blank or boring designs are avoided.

The random area selection and "boring area" filtering methods could easily be incorporated into any Mandelbrot program.

The program also demonstrates effective use of IdleAction(). It only calculates one scan line with each call, so it doesn't hog CPU time and runs well when other applications are also running. The key to achieving this was tearing apart the for() loop. Ordinarily a for() loop would calculate scan lines until the design is finished. Instead, the control variables were moved outside the function so their values get preserved between IdleAction calls. The result is a "virtual" for() loop that only performs one pass with each function call and then quits as it should when the design is done.

Although this source file is fairly simple, much of this program's functionality is provided by SDibWindow, SDib, and other library classes you'll find on this site, so the project winds up being large. Links to the needed source code are provided in the listings below where they are referenced.

Download:

Click here to download winbrot.zip (about 92 KB).

The zip contains these files that are unique to this project:

Winbrot.cpp Source code for the Borland C++ 4.0 ObjectWindows (OWL) 2.0 Windows 3.1 version. Same as the listing on this page.
winbrotprojectnodes.gif The same screenshot of the project nodes shown below on this page.
Winbrot.HPJ
Winbrot.RTF
Winbrot.HLP
Files for building the program's Windows 3.1 WinHelp file, plus the precompiled Help file. The RTF file is based on my helpfile.dot MSWord template.
Winbrot.RC
Winbrot.RH
The Windows resource definitions.
Winbrot.INI The Windows 3.1 .ini file in which the program records the location of its data files.
COPYING.TXT GNU GPL License Version 2, viewed best in Word or WordPad.
GRAYSCAL.MAP A gray scale color map file.
LIBRARY.CPP A module that pulls into the project the library classes it needs.
BORING.CPP Legacy code with an alternate (and not very good) method of determining which regions will be too boring to display.
MBROT.CPP The MSDOS version of the program. Rudimentary. Uses Borland BGI graphics.
JBrot The Java conversion of the program. It is functional, but not all of Winbrot's features are implemented.

This screenshot of the project nodes in the Borland C++ 4.0 IDE might help you set up the project nodes.

Screenshot showing the project nodes in the Borland C++ 4.0 IDE

 

Click to view full size: Mandelbrot set area. Click to view full size.

winbrot.cpp

/*	winbrot.cpp			3/12/03
	Copyright (C)1993-2001, 2003, 2006 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 2 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

	Calculates and displays regions of the Mandelbrot set.
	Can also display graphics files stored in my .PIC format, using 256 colors.
	Target is Win32 (=Win32s subsystem), WHICH IS MORE THAN TWICE AS FAST AS WINDOWS 3.1.

	Two features set this program apart from many other Mandelbrot calculators:
	1) Auto-run mode, in which it creates new random designs at timed intervals. You don't
	   have to hunt around and zoom in manually. The random designs are in random areas of
	   the M-set and are at randomly calculated magnifications.
	2) The random designs are filtered so only interesting ones are displayed.

	It also demonstrates effective use of IdleAction(). It only does a small amount
	of processing with each call, so it runs well when other applications are also running.

This program is distributed with only one color map (.MAP) file, a 256-shade gray scale, but it
uses the same "color map" file format (.MAP) as the popular program FRACTINT.
FRACTINT not only calculates regions of the Mandelbrot set, but many other fractals, and
it is a good educational tool. If you download FRACTINT, its .MAP files will work
very well with this program.

------
To Do:

If I can switch to BMP as the standard format,
I can abandon wavgpic and picxfrm routines, and use LogiTech or Corel on the BMPs
directly.  One pitfall is that those pgms might rearrange color tables, such as
to put often used colors earlier in the palette.  TEST.  Also test TDib's ability to load
from exported BMP files from those pgms.  Previously it didn't work for one of the two,
but I may have done it wrong.  You can use TDebug (and maybe Corel)
to look at the BITMAPINFOHEADERs, etc.

check what WOULD be required to allow multiselect in CmFileOpen

consider a mode where, after completing a new random design, you keep calculating lines
at the bottom, and scrolling lines off the top (by transferring scanlines),
so the image scrolls, creating motion, like a movie of the Mset.  Use a very high
magnification, or you'll get into a boring region too quickly.  But like auto-avgpic,
it's an entirely new mode that IdleAction must handle, and maybe not worth the trouble.

//-----------------------------------
From W3D :
--create a new topo .map using same coloring, but with colors going
  from black to blue, brown, green, gray, white.  within each category, shades
  increase in lightness.
for area-fill (height): set color by height, then modify it for nearness.  try to use both.

methods from rotpic were intended to be used here.
start by bringing over trig functions and calculate for 90 degrees.
see wshowfs STransform for possible applicability here.  Even though its
calcs are for x,y rotation around z axis, you should be able to
use them for any rotation by swapping axes around.

read Fractint on how the glasses color maps are supposed to be used.
if it looks promising, try modifying the colors to work with my 3d glasses.
(find the exact colors that match my glasses: I think I tried it, and found that the
glasses themselves are pure colors (255,0,0 etc.), but that that might not mean those
are the proper colors to *use* for drawing)

end W3D
//-----------------------------------
FROM WAVGPIC.CPP
You can now load and use a BMP file as though it were a PIC file, so you can now
create manually (in Logitech or Corel) files for averaging or for 3d use.
Remember, the BMP must have exactly 256 colors.  the required initial translation from color
TO height is arbitrary, depending only on where each color is in the color table,
having nothing to do with the BMP's content.

BOOL autocycle;			// starts or stops IdleAction from averaging each pass
Could make averaging a Winbrot mode, something IdleAction can do instead of calculating
new random areas, but it would require option of randomizing Heights every few passes,
since it only takes about 8 passes to completely level an image.  Could alternatively
ask user on entry how many passes to make.

bring over some of the changes from life.c  (I don't remember which ones I meant.)

END FROM WAVGPIC.CPP
--------------------

------
Notes:
--Periodically copy the latest tested versions of .EXE and .HLP to C:\UTIL because it is the
  FileManager viewer for .PIC files.
--WINBROT.6 WAS THE LAST VERSION BEFORE SDIBWINDOW USAGE, AND WORKED PERFECTLY. (But so does this one.)
--DO NOT change the .PIC file format so that the .inf file is part of its header.
--DOS and WIN version .INF files are NOT compatible.
--This program now contains ALL the routines from W3D.CPP and WAVGPIC.CPP (both now obsolete).

*/
#include <owl\owlpch.h>
#include <owl\opensave.h>
#include <owl\chooseco.h>
#include <owl\inputdia.h>
#include <owl\edit.h>
#include <iomanip.h>
#include <complex.h>
#include <bwcc.h>
#include "c:\bcs\my.h"
#include "c:\bcs\library\filearay.h"
#include "c:\bcs\library\stopwatc.h"
#include "c:\bcs\library\doubrect.h"
#pragma hdrstop

#include "c:\bcs\library\sdibwin.h" // don't precomp because it changes often
#include "winbrot.rh"				// resource definitions header

typedef DoubleRect MandelRect; 		// appropriate name for its use in this application

const char AppName[] = 	"Winbrot";
char INIFilename[] = 	"winbrot.ini";
char HelpFilename[] = 	"winbrot.hlp";

//////////////////////////////////////////////////////////////////////////////
class SMandelWindow : public SDibWindow
{
public:
	SMandelWindow(TWindow *parent = 0);
	~SMandelWindow();

	BOOL IdleAction(long idlecount);

protected:
// 	OpTranStruct* ots;      	// pointer to Options Dialog transfer structure

	// variables
									// used in loops:
	complex c;						// the constant c of z = z * z + c
	int zpower;						// the power to which z is raised (normally 2)
	int row;						// the next dib row to be calculated
	double dx;						// x (absolute) interval each horiz. pixel
	double dy;						// y (absolute) interval each vert. pixel

	BOOL allowzoom;					// whether current display is a calculated Mandelbrot area
	BOOL isotropicxy;				// whether to keep dx == dy
	TArrayAsVector<MandelRect> prev;// list of calculated regions, for backing out
	MandelRect curr;				// currently-displayed region
	uchar* PicLinebuf;				// escape times for 1 line width of current dib
	uint displaysecs;				// period that a completed design remains on screen

	// normal member functions
	BOOL OpenFile(const string& filename);	// open file, method depends on fileext
	BOOL LoadINF(const string& filename);	// read and display an .INF file
	MandelRect randomarea();				// returns a random non-boring area within m-set
	BOOL calcmbrot();						// calc & display an area within the set
	int calcescapetime(complex& c);			// escape time for a single point
	void quadrantzoom(WPARAM cmd);			// zoom into a specified quadrant
	void setcurr(const MandelRect& m);		// set curr to m and prepare to calc the region

	// overridden virtuals
	BOOL CanClose();

	// event handlers.  response table functions should be protected
	void EvSize(UINT sizeType, TSize& size);
	void EvLButtonUp(UINT, TPoint&);
	void CmHelpAbout();
	void CmFileNew();				// calculate design for a random area
	void CmFileOpen();				// select an .inf or .pic file for display
	void CmFileSaveAs();			// save current design to a file
	void CmFileCalcAgain();			// recalculate curr and display
	void CmFilePrevious();			// calculate and display previous region
	void CmFileFlip();				// flip current region and recalculate
	void CmFileRestart();			// start again with whole mbrot region
	void CmFileStop();				// stop in-progress calc and clean up
	void CmViewIsotropicXY();   	// toggles dx = dy
	void CmViewZPower();			// set power of Z
	void CmViewSetDisplaySecs();	// # of seconds before auto-new

DECLARE_RESPONSE_TABLE(SMandelWindow);
};
DEFINE_RESPONSE_TABLE1(SMandelWindow, SDibWindow)
	EV_WM_SIZE,
	EV_WM_LBUTTONUP,
	EV_COMMAND(CM_HELPABOUT,CmHelpAbout),
	EV_COMMAND(CM_FILENEW,CmFileNew),
	EV_COMMAND(CM_FILEOPEN,CmFileOpen),
	EV_COMMAND(CM_FILESAVEAS,CmFileSaveAs),
	EV_COMMAND(CM_FILECALCAGAIN,CmFileCalcAgain),
	EV_COMMAND(CM_FILEPREVIOUS,CmFilePrevious),
	EV_COMMAND(CM_FILEFLIP,CmFileFlip),
	EV_COMMAND(CM_FILERESTART,CmFileRestart),
	EV_COMMAND(CM_FILESTOP,CmFileStop),
	EV_COMMAND(CM_VIEWISOTROPICXY, CmViewIsotropicXY),
	EV_COMMAND(CM_VIEWZPOWER, CmViewZPower),
	EV_COMMAND(CM_VIEWSETDISPLAYSECS, CmViewSetDisplaySecs),
	EV_COMMAND_AND_ID(CM_VIEWZOOMOUT,quadrantzoom),
	EV_COMMAND_AND_ID(CM_VIEWZOOMTOPLEFT,quadrantzoom),
	EV_COMMAND_AND_ID(CM_VIEWZOOMTOPRIGHT,quadrantzoom),
	EV_COMMAND_AND_ID(CM_VIEWZOOMBOTTOMLEFT,quadrantzoom),
	EV_COMMAND_AND_ID(CM_VIEWZOOMBOTTOMRIGHT,quadrantzoom),
	EV_COMMAND_AND_ID(CM_VIEWZOOMCENTER,quadrantzoom),
END_RESPONSE_TABLE;
//----------------------------------------------------------------------------
// constructor
SMandelWindow::SMandelWindow(TWindow *parent) : SDibWindow(parent), prev(20,0,20)
{
// Attr.X = 100;	// set startup attributes (to use, requires frame->shrink = TRUE)
// Attr.Y = 100;	// these seem to be ignored (you set frame's)
Attr.W = 200;		// these two work (frame shrinks to them)
Attr.H = 200;

// pointers
// ots = new OpTranStruct;
PicLinebuf = new uchar[dib->Width()];

// other variables
isotropicxy = TRUE;
allowzoom = FALSE;			// no zooming: nothing displayed
dx = dy = 0.;
zpower = 2;
displaysecs = 0;			// allows loading from command line argument at startup
							// (also causes initial random design at startup, now ok)
row = MAXINT;				// set to the flag for "done", to prevent auto-calculation

sw.start();					// start timing so a new design appears at startup

}                         	//constructor
//----------------------------------------------------------------------------
// destructor
SMandelWindow::~SMandelWindow()
{
CmFileStop();

// delete ots;
delete[] PicLinebuf;
}							//destructor
//----------------------------------------------------------------------------
// RELEASED left mouse button after defining a selection area.
void SMandelWindow::EvLButtonUp(UINT modkeys, TPoint& point)
{
SDibWindow::EvLButtonUp(modkeys,point);	// this saves start and end points of drag
if(DragStart == DragEnd)                // prevent zero-area selection
	return;

if(allowzoom)			// IF CURRENT DISPLAY IS A CALCULATED MANDELBROT REGION
{
	SetParentCaption(string(GetApplication()->GetName()) + " - New Zoom View");
	if(fittowindow)
	{
		// translate screen percents to percents of curr
		setcurr(MandelRect(curr.left + DragPct.left   * curr.Width(),
						   curr.top  - DragPct.top    * curr.Height(),
						   curr.left + DragPct.right  * curr.Width(),
						   curr.top  - DragPct.bottom * curr.Height() ));
	}
	else
	{
		// translate the selected screen rectangle to a mandelbrot-scaled rectangle
		// everything is relative to left and top, which are always valid and known.
		// if window is larger than dib and user dragged outside the design,
		// the new region will simply include an area that wasn't in the previous design.
		setcurr(MandelRect(curr.left + (dx * DragRect.TopLeft().x),
						   curr.top  - (dy * DragRect.TopLeft().y),
						   curr.left + (dx * DragRect.BottomRight().x),
						   curr.top  - (dy * DragRect.BottomRight().y)) );
	}
}
else 	// WRITE SELECTED PORTION OF DISPLAYED BMP TO DISK FILE AS A .PIC
{
	if(fittowindow)
	{
		// translate screen percents to percents of dib.  destroys orig value.
		// Normalize() fixes DragPct being upside down compared to a TRect.
		DragRect = TRect(DragPct.left   * dib->Width(),
						 DragPct.top    * dib->Height(),
						 DragPct.right  * dib->Width(),
						 DragPct.bottom * dib->Height() ).Normalize();
	}
	else
	{
		// DragRect, which is in client coordinates, IS already the area we want to save!
	}
	// the write will include only the portion inside the dib.
	string tempfile = DATADir + "\\area.pic";
	dib->WriteFilePIC(tempfile,DragRect);
	OpenFile(tempfile);						// immediate load with 3d options

	// old: just inform
	// string s = string("The selected region of the screen image\n") +
	// 		   "has been written to the file " + tempfile;
	// MessageBox(s.c_str(),"Graphic Area Saved",MB_OK);
	// Invalidate(FALSE);  	// only if not zooming, clean up display
}
}       	            	//EvLButtonUp
//----------------------------------------------------------------------------
// zoom into a specified quadrant
void SMandelWindow::quadrantzoom(WPARAM cmd)
{
if(allowzoom)
{
	SetParentCaption(string(GetApplication()->GetName()) + " - New Quadrant Zoom");
	switch(cmd)	// these don't care about fittowindow: a quadrant is the same either way
	{
		case CM_VIEWZOOMOUT: 		 setcurr(curr.IsCenterQuadOf()); break;	// zooms out
		case CM_VIEWZOOMTOPLEFT: 	 setcurr(curr.TopLeftQuad()); break;	// the rest zoom in
		case CM_VIEWZOOMTOPRIGHT: 	 setcurr(curr.TopRightQuad()); break;
		case CM_VIEWZOOMBOTTOMLEFT:  setcurr(curr.BottomLeftQuad()); break;
		case CM_VIEWZOOMBOTTOMRIGHT: setcurr(curr.BottomRightQuad()); break;
		case CM_VIEWZOOMCENTER:		 setcurr(curr.CenterQuad()); break;
	}
}
else	// for now, for BMP, you must use mouse drag to define area
{
	// method (but not worth doing):
	// copy dib->Rect() to a DoubleRect; calc its quadrant, according to cmd;
	// make a TRect from the DoubleRect, and WriteFilePIC() using it.
	MessageBox("Use mouse to select area of PIC.","Cannot Quadrant-Select a PIC/BMP",MB_OK);
	return;
}
}                    		//quadrantzoom
//----------------------------------------------------------------------------
// set curr and initialize all variables to begin calculating the new region.
void SMandelWindow::setcurr(const MandelRect& newcurr)
{
CmFileStop();						// stop calc, delete random.pic if it's partial
curr = newcurr;                   	// this also normalizes
prev.Add(curr);						// add to "previous" list immediately
c = complex(curr.left,curr.top);	// calculation will start at top left corner
row = 0;							// init, and signals that calculation should start
sw.reset();							// stop timing previous display interval

makenewdib();      					// recreate it for screen size at this moment
delete[] PicLinebuf;				// resize to match dib width
PicLinebuf = new uchar[dib->Width()];
dx = curr.Width()  / (double)(dib->Width() - 1);	// x interval each horiz. pixel
dy = curr.Height() / (double)(dib->Height() - 1);	// y interval each vert. pixel
if(isotropicxy)
	dx = dy = max(dx,dy);
	// this forces dx:dy to be 1:1, by setting both to be the larger of the two,
	// which will ensure that *at least* the entire selected area is displayed.
	// Not only that, but since the loops count PIXELS, you'll always fill the screen.
	// If isotropicxy is OFF, you'll fill it by calculating more pixels in the
	// smaller dimension, which will, for example, squeeze circles out of round.
	// If isotropicxy is ON, you'll fill it by pulling in more of the "mbrot" area
	// than curr actually specifies, but circles will stay round.
	// It is good to save curr BEFORE forcing dx == dy.		<-- ?
	// It should make it possible to switch between a distorted and nondistorted view.

// we just calculated dib to exactly fit the window, so fittowindow isn't necessary,
if(fittowindow)
	CmViewFitToWindow();
allowzoom = TRUE;

// palette must be locked during calculation, else each line is set to a different color
// scheme.  It doesn't bother the .PIC file, but the dib's colors are, and stay, wrong.
dib->Locked = TRUE;
}                 				//setcurr
//----------------------------------------------------------------------------
// toggle whether dx and dy are kept equal
void SMandelWindow::CmViewIsotropicXY()
{
isotropicxy = !isotropicxy;
CheckMenuItem(GetApplication()->GetMainWindow()->GetMenu(),CM_VIEWISOTROPICXY,
					MF_BYCOMMAND | (isotropicxy ? MF_CHECKED : MF_UNCHECKED));
}
//----------------------------------------------------------------------------
void SMandelWindow::EvSize(UINT sizeType, TSize& size)
{
SDibWindow::EvSize(sizeType,size);
// allowzoom = FALSE;		// you can no longer zoom if window doesn't match dib
}		                      	//EvSize
//----------------------------------------------------------------------------
void SMandelWindow::CmViewZPower()
{
char buf[20];
ostrstream(buf,sizeof(buf)) << zpower << ends;
if(TInputDialog(this,"Power of z (n) To Use in (z = z^n + c)","Minimum 2, Maximum 16: ",
						buf,sizeof(buf)).Execute() == IDOK)
{
	int i = atoi(buf);
	if((i >= 2) && (i <= 16))	// I tested 16: ok, but uninteresting
		zpower = i;
}
}                      			//CmViewZPower
//----------------------------------------------------------------------------
// # of seconds before auto-new
void SMandelWindow::CmViewSetDisplaySecs()
{
char buf[20];
ostrstream(buf,sizeof(buf)) << displaysecs << ends;
if(TInputDialog(this,"Time Delay Before Automatic File|New","Number of seconds (1 to 65535): ",
						buf,sizeof(buf)).Execute() == IDOK)
{
	uint i = (uint)atol(buf);
	if(i >= 1)                  // zero is reserved as a startup flag
		displaysecs = i;
}
}                      			//CmViewSetDisplaySecs
//----------------------------------------------------------------------------
// calculates the escape time for a single point (d).
// Repeatedly calculate Z = Z * Z + C until the iteration count reaches
// the maximum or until we can be certain it is heading to infinity.
// remember that escape times get stored as uchars, and (uchar)255 + 1 = 0.
// if the loop completes, you must return 255 upon exit, not 256.
//
// since z starts at (0,0), an actual iteration count of 0 is impossible
// (it can never actually abort "after" 0 iterations because at that time z IS 0),
// so it tests for z > 2.0 AFTER each iteration, to get an extra "elevation" in the .PIC file.
//
// the test you use to break loop *will* affect the shapes of the colored bands.
// (e.g. if you use Manhattan distance, corners will be squared off)
//
// Testing prior z values to detect cycles (and thus abort loop early) did work, but
// randomarea() effectively screens out all the areas where the cycle tests are any use.
//
// To calculate Julia sets or even other non-Mandelbrot variations, you probably
// have only to provide alternate versions of calcescapetime().
//
int SMandelWindow::calcescapetime(complex& d)
{
complex z(0,0), oldz(0,0);
int i, j;
for(i = 0 ; i < 255 ; i++)
{
	oldz = z;
	for(j = 1 ; j < zpower ; j++) 	// multiply once less than zpower
		z *= oldz;
	z += d;
	// keep these to remember I've tried them
	// if((abs((int)real(z)) > 2) || (abs((int)imag(z)) > 2))// whole= 1:52, distorted
	// if(abs(z) > 2.0)	// 2.0 is a sure test: see CUC p.247    whole= 2:00 minutes
	if(norm(z) > 4.0)	// norm should avoid a sqrt() call.     whole= 1:38 and identical
		break;
}
return(i);
}							//calcescapetime
//----------------------------------------------------------------------------
// returns a random non-boring area within the m-set.
MandelRect SMandelWindow::randomarea()
{
MandelRect m;								// function's return value
TRect clientrect = GetClientRect();
double aspect = (double)clientrect.Width() / clientrect.Height();
double tleft, tright, ttop, tbottom, height, width;
while(1)
{
	// CALCULATE A RANDOM TOP LEFT POINT WITHIN THE "MBROT" AREA
	// THIS VERSION WORKED WELL FOR ZPOWER == 2 (STANDARD)
	// tleft = ((double)random(25001) - 20000.) / 10000.;	// -2.0 to +0.50
	// ttop = ((double)random(25001) - 12500.) / 10000.;	// -1.25 to +1.25
	// THIS VERSION TO ACCOMODATE ANY ZPOWER
	tleft = ((double)random(40001) - 20000.) / 10000.;	// -2.0 to +2.0
	ttop =  ((double)random(40001) - 20000.) / 10000.;	// -2.0 to +2.0
	do
	{
		// THE FOLLOWING GIVES A RANDOM HEIGHT 0. TO +2.5, PROBABLY TOO BIG
		// height = ((double)lrandom(2500000L)) / 1000000.;
		// THE NUMBERS USED HERE DETERMINE HOW CLOSE A ZOOM IS POSSIBLE
		// GIVEN A/B BELOW, HEIGHT WILL BE BETWEEN 1/B AND (A-1)/B.
		// next line was used for a long time, but many designs are similar at this scale.
		// height = ((double)random(10001)) / 100000.;	// .00001 to .1	good
		// height = ((double)lrandom(10000001L)) / 1e9;	// 1e-9 to .01	better
		height = ((double)lrandom(1000000001L)) / 1e11;	// 1e-11 to .01	great!
	}
	while(height == 0.);
	width = aspect * height;		// set the width
	tbottom = ttop - height;		// calculate the bottom right coordinates
	tright = tleft + width;
	m = MandelRect(tleft,ttop,tright,tbottom);	// create the proposed area

	// but reject it if a sampling of its points all have very similar escape times
	int r = calcescapetime(m.Center());			// a reference point
	int minimum = 8;							// minimum required escape time variation
	if( (abs(r - calcescapetime(m.TopLeft())) 		< minimum)	&&
		(abs(r - calcescapetime(m.TopRight())) 		< minimum) 	&&
		(abs(r - calcescapetime(m.BottomLeft()))	< minimum) 	&&
		(abs(r - calcescapetime(m.BottomRight()))	< minimum)	&&
		(abs(r - calcescapetime(m.MidLeft())) 	 	< minimum) 	&&
		(abs(r - calcescapetime(m.MidRight())) 		< minimum)	&&
		(abs(r - calcescapetime(m.MidTop()))    	< minimum) 	&&
		(abs(r - calcescapetime(m.MidBottom()))		< minimum) )
			continue;
	break;							// if you made it to end, you can exit loop
}
SetParentCaption(string(GetApplication()->GetName()) + " - Random Area");
return(m);
}							//randomarea
//----------------------------------------------------------------------------
// calculate escape times for 1 scanline of points in a new picture.
// This fn is called by IdleAction(), and each entry to it calculates only 1 scan line,
// so the program coexists well with other applications and does not hang up Windows.
// returns TRUE if another one should be generated, FALSE if not (quit)
// (return value was from DOS version, not currently used here)
// should do a careful check of the maxx, maxy, dx, dy calculations and usage (1 off?)
BOOL SMandelWindow::calcmbrot()
{
// if(IsIconic())					// minimized, no point in doing anything
// 	return(FALSE);
if(row >= dib->Height()) 		// no design in process (either none, or it was finished)
	return(TRUE);
								// if it falls through, calc the current line (just one)
// fittowindow must remain OFF during calculation so any partial-screen Invalidate() region
// calculated on the basis of the dib is accurate for the screen. (is this true?)
// if user did anything to turn allowzoom OFF, we don't toggle it back on here. maybe could?
// paper note said:
// "rem out and test.  also try invalidating using GetClientRect().Width() below."
if(fittowindow)
	CmViewFitToWindow();

TDibDC dibdc(*dib);				// for drawing to dib
c = complex(curr.left,imag(c));	// x-axis back to zero (left side)
// int i;
int maxx = dib->Width();								// calc once for speed
for(int column = 0 ; column < maxx ; column++) 			// each entry calculates a row
{
	PicLinebuf[column] = (uchar)calcescapetime(c);		// write to line buffer
	c += complex(dx,0);									// increment x axis (move right)
}
dib->SetScanLineBytes(row,PicLinebuf);					// new: set BMP bytes directly
c -= complex(0,dy);										// decrement y axis (move down)
// invalidate only the row we just drew (seems to be working properly!)
InvalidateRect(TRect(TPoint(0,row),TSize(dib->Width(),1)),FALSE);
row++;								// this was the end when it was a while() loop
if(row >= dib->Height()) 			// if we just finished the design,
{
	CmFileStop();					// reset variables, etc.
	// write the PIC file: at this point you are SURE its bytes are the true escape times
	// the pgm has had no opportunity to inadvertently change them (SetPixel, GetColor, etc.)
	// save in case escape times ever seem corrupted in later saves.
	// dib->WriteFilePIC("TEMP\\random.pic");	// save no longer is automatic
	Invalidate(FALSE);		// Invalidate entire screen at design end, to ensure it's right
	sw.start();				// start new timing interval.
}
return(TRUE);
}								//calcmbrot
//----------------------------------------------------------------------------
// originally, and usually, selects and displays a random region.
// but if there are any files in FileList, it loads the next one.
// (a feature currently undocumented, but useful at startup),
void SMandelWindow::CmFileNew()
{
BOOL gotone = FALSE;
while(FileList.GetItemsInContainer())	// If a file fails, go on to the next one
{
	// copy a name from the list, delete it from list, try to use it to initialize
	gotone = OpenFile(FileList.GetNext());
	if(gotone)
		break;
}
if(!gotone)					// last resort
	setcurr(randomarea());	// the original CmFileNew had only this 1 line
}        					// CmFileNew
//----------------------------------------------------------------------------
// recalculate curr and display. must re-init, even though it's the same curr
void SMandelWindow::CmFileCalcAgain()
{
setcurr(curr);										// auto-adds it to prev
if(prev.GetItemsInContainer() >= 2)                 // if it isn't first entry, (if it wasn't
	prev.Destroy(prev.GetItemsInContainer() - 1);	// user's first command), remove it
}
//----------------------------------------------------------------------------
// display the previous area (the one before the currently displayed one).
// As you back up over entries, they are deleted from the list. You can't go forward thru list.
// The last prev entry will always be the one currently being calculated or displayed.
void SMandelWindow::CmFilePrevious()
{
if(prev.GetItemsInContainer() >= 2)
{
	setcurr(prev[prev.GetItemsInContainer() - 2]); 	// set to the one BEFORE the current
	prev.Destroy(prev.GetItemsInContainer() - 1);	// now remove its duplicate entry
	prev.Destroy(prev.GetItemsInContainer() - 1);	// AND remove the one you backed up over
													// (i.e. the currently displayed one)
	SetParentCaption(string(GetApplication()->GetName()) + " - Previous (#" +
					tostring(prev.GetItemsInContainer()) + ")");
	return;
}
CmFileCalcAgain();			// if no previous, just stay with current
}                       	//CmFilePrevious
//----------------------------------------------------------------------------
// flip current region so it's upside down and recalc. (M-set symmetrical above/below y axis)
// (horizontal flip not so easy: would have to flip the BMP)
void SMandelWindow::CmFileFlip() { setcurr(curr.FlippedVertical()); }
//----------------------------------------------------------------------------
// start fresh with whole mbrot region, in a square area that holds a circle of radius 2
// (adapted for use with any zpower)(old was just MandelRect())
void SMandelWindow::CmFileRestart() { setcurr(MandelRect(-2,2,2,-2)); }
//----------------------------------------------------------------------------
// stop in-progress calc and clean up
void SMandelWindow::CmFileStop()
{
row = MAXINT;				// set to the flag for "done", to prevent auto-calculation
dib->Locked = FALSE;		// ok to re-enable color rotation, etc.
}							//CmFileStop
//----------------------------------------------------------------------------
// read and display an .INF file
BOOL SMandelWindow::LoadINF(const string& infilename)
{
MandelRect m;
ifstream infile(infilename.c_str());
if(!(infile >> m))			// if the read failed, file's no good (or none)
	return(FALSE);
SetParentCaption(string(GetApplication()->GetName()) + " - " + infilename);
setcurr(m);					// succeeded, so copy it
return(TRUE);
}							//LoadINF
//----------------------------------------------------------------------------
// open file, method depends on fileext.
// could allow .MAP files on command line if you allow .MAP here:
// add the MAP to MapFiles list AND load it into dib immediately. set result to FALSE
// because no design was actually loaded.
BOOL SMandelWindow::OpenFile(const string& filename)
{
CmFileStop();					// STOP any calculation in progress
sw.reset();						// stop auto-timer: a file's display time shouldn't be limited
string s(filename);
if(!s.length())
	return(FALSE);
if(s.contains(".pic") || s.contains(".bmp"))
{
	GetApplication()->GetMainWindow()->Show(SW_SHOWMAXIMIZED);
	allowzoom = FALSE;			// only a PIC or BMP, no bounding data
}
BOOL result = FALSE;
if(s.contains(".pic"))
	result = LoadPIC3d(s);		// this is why row must be MAXINT (NO calc allowed for PIC)
else
	if(s.contains(".inf"))
		result = LoadINF(s);	// this will reset row to 0 to start new calc
	else
		if(s.contains(".bmp"))
			result = LoadBMP(s);
		else
		{
			MessageBox("Can only load .INF, .PIC, .BMP files.",
						GetApplication()->GetName(),MB_OK);
			return(FALSE);
		}
if(!result)						// load error
{
	s.to_upper();
	s.prepend("Error loading file: ");
	MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
}
return(result);
}							//OpenFile
//----------------------------------------------------------------------------
// user can specify file to open and display
void SMandelWindow::CmFileOpen()
{
chdir(DATADir.c_str());
static TOpenSaveDialog::TData PICfiledata(OFN_FILEMUSTEXIST,		// flags ok
	"All Loadable Files|*.INF;*.PIC;*.BMP|Info files|*.INF|Graphics Files|*.BMP;*.PIC|"
	"PIC Files|*.PIC|Bitmap Files|*.BMP|All Files|*.*|",0,0,"INF");
if((TFileOpenDialog(this,PICfiledata)).Execute() != IDOK)
	return;
char buf[MAXPATH];     		// see also Get/SetCurrentDirectory (Win32 only)
getcwd(buf,MAXPATH);
DATADir = buf;
OpenFile(PICfiledata.FileName);
}               			//CmFileOpen
//----------------------------------------------------------------------------
// Write current design to a file
void SMandelWindow::CmFileSaveAs()
{
chdir(DATADir.c_str());
// static so that the name of the last-saved file is the default
// #error: note this would be shared by all class objects if you allow multiples
static TOpenSaveDialog::TData bmpfiledata(							// flags ok
	OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
	"All Loadable Files|*.INF;*.PIC;*.BMP|Info files|*.INF|Graphics Files|*.BMP;*.PIC|"
	"PIC Files|*.PIC|Bitmap Files|*.BMP|All Files|*.*|",0,0,"INF");
if(TFileSaveDialog(this,bmpfiledata).Execute() != IDOK)	// select file
	return;
char buf[MAXPATH];
getcwd(buf,MAXPATH);
DATADir = buf;
string s = bmpfiledata.FileName;
BOOL result = FALSE;
if(s.contains(".bmp"))
	result = dib->WriteFile(s.c_str());
else
	if(s.contains(".pic"))
		result = dib->WriteFilePIC(s);
	else
		if(s.contains(".inf"))
		{
			ofstream outfile(s.c_str());
			outfile << curr << endl;
			result = !outfile.fail();
		}
		else
			MessageBox("Can only save .INF, .PIC, .BMP files.",
						GetApplication()->GetName(),MB_OK);
if(!result)						// load error
{
	s.to_upper();
	s.prepend("Error loading file: ");
	MessageBox(s.c_str(),GetApplication()->GetName(),MB_OK);
}
}                      		//CmFileSaveAs
//----------------------------------------------------------------------------
void SMandelWindow::CmHelpAbout()
{
	MessageBox("Copyright 1993-2003 Steven Whitney",GetApplication()->GetName(),MB_OK);
}
//----------------------------------------------------------------------------
BOOL SMandelWindow::CanClose()
{
if(!SDibWindow::CanClose())
	return(FALSE);
return(TRUE);
}								//CanClose
//----------------------------------------------------------------------------
// The key to getting IdleAction() called repeatedly is generating a Windows Message
// within each IdleAction call. Here it is done using a call to Invalidate() (which
// in this case is done within calcmbrot() or other fns).
BOOL SMandelWindow::IdleAction(long idlecount)
{
// if time interval elapsed, (only possible when not calculating), autostart a new design.
if(sw.split() >= displaysecs)
{
	// here so you only do it if pgm is in "auto-run" mode (not for every CmFileNew)
	if(MapFiles.GetItemsInContainer())		// choose a .MAP file at random, for variety
		dib->LoadColors(MapFiles.Random());
	if(!random(2))					// doubles the available palettes
		dib->ReverseColors();       // avoid the Invalidate() in CmColorsReverse();
	dib->RotateColors(random(256));	// whichever palette you have, rotate it a bit
	if(displaysecs == 0)			// "startup" flag: it is 0 ONLY at startup
	{
		displaysecs = MAXUINT;		// set to actual default = very long time
	}
	if(autorotatecolors)			// only if user has turned autorotate ON, set a direction
	{
		autorotatecolors = random(2);	// set to 0 or 1, then 1 more to get 1 or -1,
		CmViewAutoRotateColors();		// and force menu item to match
	}
	CmFileNew();				// autostart a new design. (is before calcmbrot() correct?)
}
calcmbrot();					// if calculating, calc 1 line.

// this handles palette rotation, if any. (must FOLLOW calcmbrot?)
SDibWindow::IdleAction(idlecount);	

// this came from SDibWindow::CmViewFitToWindow().  Not sure where to put it.
// It will become unnecessary.
// if(fittowindow)
// 	allowzoom = FALSE;

return(TRUE);
}                 			//IdleAction
//----------------------------------------------------------------------------
// 						end class SMandelWindow
//////////////////////////////////////////////////////////////////////////////
// The application object
class TMyApp : public TApplication
{
public:
	TMyApp(const char far *title) : TApplication(title) {}	// title used in case of error
	~TMyApp() {}

protected:
	virtual BOOL IdleAction(long idlecount);
	virtual void InitMainWindow();
};
//----------------------------------------------------------------------------
BOOL TMyApp::IdleAction(long /* idlecount */)
{
TApplication::IdleAction(0);
return TRUE;	 	// it is important to return TRUE here, or this won't be called again
}
//----------------------------------------------------------------------------
void TMyApp::InitMainWindow()
{
TFrameWindow* frame = new TFrameWindow(0,GetName(),new SMandelWindow,TRUE);
frame->Attr.X = 150;      					// set location on the screen;
frame->Attr.Y = 150;                        // the size is handled by the SMandelWindow
frame->AssignMenu(TResId(MENU_1));
frame->Attr.AccelTable = TResId(MENU_1);

nCmdShow = SW_SHOW;
SetMainWindow(frame);
EnableCtl3d(TRUE);
}							//InitMainWindow
//----------------------------------------------------------------------------
// 					end class TMyApp
//////////////////////////////////////////////////////////////////////////////
// OwlMain
int OwlMain(int /*argc*/, char** /*argv*/)
{
randomize();					// seems to work here
string::set_case_sensitive(0);

TMyApp* tma = new TMyApp(AppName);

// Run() calls:
// InitApplication(), InitInstance() { (which calls InitMainWindow() },
// then displays main window and operates the message loop.
int retval = tma->Run();
delete tma;
return(retval);
}

library.cpp

/*	library.cpp			11-15-01
	This file is part of the Winbrot project.
	Copyright (C)2000-01 Steven Whitney.
	Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
	Initially published by http://25yearsofprogramming.com.

	library file for winbrot.ide

*/
#include <owl\owlpch.h>
#include <owl\opensave.h>
#include <owl\chooseco.h>
#include <owl\inputdia.h>
#include <owl\radiobut.h>
#include <owl\edit.h>
#include <iomanip.h>
#include <bwcc.h>
#include <complex.h>
#include "c:\bcs\my.h"
#pragma hdrstop

#include "c:\bcs\library\sdibwin.h"

#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\filearay.cpp"
#include "c:\bcs\library\stopwatc.cpp"
#include "c:\bcs\library\sdib.cpp"
#include "c:\bcs\library\doubrect.cpp"

 

 

 

Valid HTML 4.01 Transitional Valid CSS
View content labeling at ICRA.
Copyright ©2008 Steven Whitney. Last modified 02/27/2008.