Click here to Skip to main content
Click here to Skip to main content

C# Simple Mandelbrot with Flicker Free Zoom

, 8 Sep 2013
Rate this:
Please Sign up or sign in to vote.
C# Simple Mandelbrot with Zoom

Introduction

This is a very simple C# graphics program that displays the famous Mandelbrot set, lets you zoom in with a simple selection rectangle, and shows how to smooth the colours, and not flicker.

Background

Most people are familiar with the Mandelbrot Set, a easy to compute, but very pretty fractal. Typical source code looks like this. The linked code works fine, but for the C# programmer you will generally run into two problems: banded colours, and horrible flickering when you implement a selection rectangle.

I'm assuming you can create a project, drag tools onto a form, and program simple event handlers.

Using the Code

This is C# code framework 4.0 using Visual Studio 10.

My program is a simple form with a panel to draw the Mandelbrot set on, a draw button to re-draw the current image, and a reset button to start over and unzoom.

We want to set a few variables when our program starts:

public FormMandelbrot()
	{
	InitializeComponent();
	cx0 = -2.0;  // these values show the full mandelbrot set
	cx1 = 0.5;
	cy0 = -1.0;
	cy1 = 1.0; 
	 // where we store the panel background
	Map = new Bitmap(panelMain.Width, panelMain.Height);
	} 

Draw button event code looks like:

// draw the currently selected mandelbrot section
private void buttonDraw_Click(object sender, EventArgs e)
	{ 
	doDrawMandelbrot = true;
	panelMain.Refresh();
	} 
 

You never actually do any drawing in event handlers. You just let the program know what it needs to know to draw, and call refresh to do trigger actually drawing something in the Paint event.

Reset button event code looks like:

private void buttonReset_Click(object sender, EventArgs e)
	{
	// set the rectangle to draw back to the whole thing
	cx0 = -2.0;
	cx1 = 0.5;
	cy0 = -1.0;
	cy1 = 1.0;
	doDrawMandelbrot = true;
	panelMain.Refresh();
	} 

And the Paint event handler:

private void panelMain_Paint(object sender, PaintEventArgs e)
{
	if (doDrawMandelbrot)
		{
		DrawMandelbrot(cx0, cy0, cx1, cy1, e.Graphics);
		doDrawMandelbrot = false;
		} 
}  

Points of Interest

The only way to stop flicker is to make your own class from panel that has only draws when you draw in Paint. This is done by setting the "style" which can't be done in a normal Panel class.

// this has to be at the bottom of the source file or the form designer won't work
	public class MyPanel : System.Windows.Forms.Panel
		{
		// a non-flickering panel. It doesn't draw its own background
		// if you don't do this the panel flickers like crazy when you resize th
		// selection rectangle
		public MyPanel()
			{ 
			this.SetStyle(
				System.Windows.Forms.ControlStyles.UserPaint |
				System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
				System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer,
				true);
			}
		}  

Once you have this new class set up, you have to change the declaration of the panel to use this new one. In forma1.designer.cs, change panelMain from a normal panel to your new panel:

private void InitializeComponent()
	{
	this.buttonDraw = new System.Windows.Forms.Button();
	this.buttonReset = new System.Windows.Forms.Button();
	this.panelMain = new Mandelbrot.MyPanel(); 

and farther down..

		private System.Windows.Forms.Button buttonDraw;
		private System.Windows.Forms.Button buttonReset;
		//private System.Windows.Forms.Panel panelMain;
		private MyPanel panelMain; 

But that is only half the no flicker battle. Drawing the Mandelbrot takes seconds which would cause a bit of flicker if it had to do that each time we moved the mouse for the selection rectangle. So we save it to a bitmap, draw to a bitmap, then copy the bitmap to the visible bitmap as we move the mouse. No flicker.

So mouse move event looks like:

 private void panelMain_MouseMove(object sender, MouseEventArgs e)
	{
	if (isMouseDown)
		{
		// get new coords of rect
		mx1 = e.X;
		my1 = e.Y;
		panelMain.Refresh();
		}
	} 

and the paint now looks like this:

private void panelMain_Paint(object sender, PaintEventArgs e)
{
Graphics g;
	if (isMouseDown)
		{
		Pen penYellow = new Pen(Color.ForestGreen);
		// restore background, then draw new rectangle, both to the offscreen bMap
		bMap = (Bitmap )bMapSaved.Clone();
		g = Graphics.FromImage(bMap);
		g.DrawRectangle(penYellow, mx0, my0, mx1-mx0, my1-my0);
		e.Graphics.DrawImageUnscaled(bMap, 0, 0); // copy whole thing to visible screen
		}
	else
		{
		if (doDrawMandelbrot)
			{
			g = Graphics.FromImage(bMap);
			// draw it to our background bitmap
			DrawMandelbrot(cx0, cy0, cx1, cy1, g); 
			// display it on the panel
			e.Graphics.DrawImageUnscaled(bMap, 0, 0); 
			 // save our background; the current mandelbrot image
			bMapSaved = (Bitmap)bMap.Clone(); 
			doDrawMandelbrot = false;
			}
		else
			{
			e.Graphics.DrawImageUnscaled(bMap, 0, 0);
			}
		}
	} 

The other point is how to scale/zoom. The main drawing function, not shown, takes as parameters which part of the set to draw. So all you have to do is convert the points you select with the mouse to the points in the set:

private void panelMain_MouseUp(object sender, MouseEventArgs e)
	{
	// save where the end of the selection rect is at
	isMouseDown = false;
	mx1 = e.X;
	my1 = e.Y;
			
	/*
	 * cx0, cy0 and cx1, cy1 are the current extent of the set
	 * mx0,my0 and mx1,my1 are the part we selected
	 * do the math to draw the selected rectangle
	 * */
	double scaleX, scaleY;
	scaleX = (cx1 - cx0) / (double )panelMain.Width;
	scaleY = (cy1 - cy0) / (double)panelMain.Height;
	cx1 = (double )mx1*scaleX + cx0;
	cy1 = (double)my1*scaleY + cy0;
	cx0 = (double)mx0 * scaleX + cx0;
	cy0 = (double)my0 * scaleY + cy0;
	doDrawMandelbrot = true;
	panelMain.Refresh(); // force mandelbrot to redraw
	} 

And we don't want horrible banded colours, we want nice smooth colours. This odd bit of math makes a "smooth" number between 0 and 1 which we can use as the hue to get a nice colour. If you look at the math link it looks very complex, but it boils down to this:

 private Color MapColor(int i, double r, double c)
	{
	double di=(double )i;
	double zn;
	double hue;
		zn = Math.Sqrt(r + c);
		 // 2 is escape radius
		hue = di + 1.0 - Math.Log(Math.Log(Math.Abs(zn))) / Math.Log(2.0); 
		hue = 0.95 + 20.0 * hue; // adjust to make it prettier
		// the hsv function expects values from 0 to 360
		while (hue > 360.0)
			hue -= 360.0;
		while (hue < 0.0)
			hue += 360.0;
		return ColorFromHSV(hue, 0.8, 1.0);
		} 

Please see the full source for a working version, and all the details.

History

  • Initial version

License

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

About the Author

arussell
Software Developer (Senior)
Canada Canada
Professional Programmer living in Beautiful Vancouver, BC, Canada.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberVolynsky Alex9-Sep-13 7:59 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140709.1 | Last Updated 8 Sep 2013
Article Copyright 2013 by arussell
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid