/// <summary>Starts calculating the mandelnumbers for the given area of the mandelbrot set.</summary> /// <param name="args">The area to calculate</param> public void Calculate(MandelAreaArgs args) { lock(locker) { // Abort any old workers Abort(); // Make sure the x axis lies in the middel of a pixel args.CenterOnXAxis(); // Create a new worker worker = new Worker(args); worker.OnWorkFinished = WorkFinished; // Determine which indexes should be calculated and which can be determined using the symmetry of the mandelbrot set int startIndex = 0; int endIndex = args.CalcArea.Width * args.CalcArea.Height; if(args.CalcAreaTop() > 0.0 && args.CalcAreaBottom() < 0.0) { if(args.CalcAreaTop() >= -args.CalcAreaBottom()) { // `endIndex` = `rows of pixels above the x-axis including the x-axis` * `width in pixels` endIndex = (int) Math.Min(endIndex, Math.Round((args.CalcAreaTop() / args.Scale + 1) * args.CalcArea.Width)); } else { // `startIndex` = `rows of pixels above the x-axis excluding the x-axis` * `width in pixels` startIndex = (int) Math.Round((args.CalcAreaTop() / args.Scale) * args.CalcArea.Width); } } // Queue the work items workFinished = new Dictionary<int, bool>((int) Math.Ceiling(((double) endIndex - startIndex) / WORK_PER_THREAD)); for(int i = startIndex; i < endIndex; i += WORK_PER_THREAD) { workFinished[i] = false; ThreadPool.QueueUserWorkItem(new WaitCallback(worker.Work), new Worker.WorkerArgs(i, Math.Min(i + WORK_PER_THREAD, endIndex))); } } }
public MandelDisplay() { // Initialize the control InitializeComponent(); // Initialiaze some fields currArea = new MandelAreaArgs(); calcedArea = new MandelAreaArgs(); mandelNumbers = null; img = null; calculating = false; mandelNumberToColor = defaultMandelNumberToColor; // Create and attach the mandelbrot calculator mandelbrot = new Mandelbrot(); mandelbrot.OnCalculationDone = onMandelbrotDoneHandler; this.Controls.Add(mandelbrot); // Connect the event handlers this.Paint += this.paint; // Some properties of the control this.ResizeRedraw = true; }
/// <summary>Constructs a Worker to calculate the mandelnumbers for a certain area</summary> /// <param name="args">The area that is to be calculated</param> public Worker(MandelAreaArgs args) { mandelNumbers = new int[args.CalcArea.Width * args.CalcArea.Height]; mandelAreaArgs = args; }
/// <summary>Calls the OnCalculationDoneHandler</summary> /// <param name="args">The area for which the calculation was done that is to be passed to the OnCalculationDoneHandler</param> /// <param name="mandelNumbers">The array of mandelnumbers that is to passed to the OnCalculationDoneHandler</param> private void CallOnCalculationDone(MandelAreaArgs args, int[] mandelNumbers) { if(onCalculationDoneHandler != null) BeginInvoke(onCalculationDoneHandler, new object[] { args, mandelNumbers }); }
/// <summary>Recalculates the mandelbrot for the current size and area</summary> public void Recalc() { // Start the new calculation if(mandelNumbers != null && currArea.Scale == calcedArea.Scale && currArea.MaxIterations == calcedArea.MaxIterations) { if(currArea.CenterX == calcedArea.CenterX && currArea.CenterY == calcedArea.CenterY && currArea.PxWidth == calcedArea.PxWidth && currArea.PxHeight == calcedArea.PxHeight) renderBitmap(); else { // Calculate the translation int transX = (int) Math.Round((calcedArea.CenterX - currArea.CenterX) / calcedArea.Scale); int transY = (int) Math.Round((currArea.CenterY - calcedArea.CenterY) / calcedArea.Scale); // Check if the already calculated area and the area that is to be calculated overlap if(calcedArea.CalcArea.X + transX < currArea.PxWidth && calcedArea.CalcArea.Y + transY < currArea.PxHeight && calcedArea.CalcArea.X + transX + calcedArea.CalcArea.Width > 0 && calcedArea.CalcArea.Y + transY + calcedArea.CalcArea.Height > 0) { // Calculate the overlapping area (in the pixel coordinates of currArea) Rectangle overlap = new Rectangle(calcedArea.CalcArea.X + transX, calcedArea.CalcArea.Y + transY, calcedArea.CalcArea.Width, calcedArea.CalcArea.Height); overlap.Intersect(new Rectangle(0, 0, currArea.PxWidth, currArea.PxHeight)); // Create a new array of mandelnumbers int[] tmpMandelNumbers = new int[overlap.Width * overlap.Height]; for(int x = 0; x < overlap.Width; ++x) for(int y = 0; y < overlap.Height; ++y) tmpMandelNumbers[x + y * overlap.Width] = mandelNumbers[x + overlap.X - transX + (y + overlap.Y - transY) * calcedArea.CalcArea.Width]; // Set the calcedArea and mandelNumber to the new values overlapping values calcedArea = currArea; calcedArea.CalcArea = overlap; mandelNumbers = tmpMandelNumbers; // Start filling the gaps calcPart(); // Create a new bitmap of the right size and copy the old one to it Bitmap newImg = new Bitmap(calcedArea.PxWidth, calcedArea.PxHeight); Graphics.FromImage(newImg).FillRectangle(Brushes.White, 0, 0, calcedArea.PxWidth, calcedArea.PxHeight); Graphics.FromImage(newImg).DrawImage(img, transX, transY); img = newImg; } else { // Just start a regular calculation if they don't overlap expectWholeArea = true; mandelbrot.Calculate(currArea); calculating = true; } } } else { expectWholeArea = true; mandelbrot.Calculate(currArea); calculating = true; } // Redraw the screen Invalidate(); }
/// <summary>Handler for when `mandelbrot` is done calculating</summary> private void onMandelbrotDoneHandler(MandelAreaArgs args, int[] result) { if(expectWholeArea) { // Remember the area for which the calculation has been done calcedArea = args; mandelNumbers = result; // Recalculate the color map if the maximum amount of iterations has changed if(colorMap.Length != calcedArea.MaxIterations + 1) { colorMap = new Color[calcedArea.MaxIterations + 1]; for(int i = 0; i <= calcedArea.MaxIterations; ++i) colorMap[i] = mandelNumberToColor(i, calcedArea.MaxIterations); } // We're done calculating calculating = false; // Render the bitmap renderBitmap(); } else { // Calculate the new calcedArea.CalcArea Rectangle newCalcArea = new Rectangle(Math.Min(calcedArea.CalcArea.X, args.CalcArea.X), Math.Min(calcedArea.CalcArea.Y, args.CalcArea.Y), 1, 1); newCalcArea.Width = Math.Max(calcedArea.CalcArea.Right, args.CalcArea.Right) - newCalcArea.X; newCalcArea.Height = Math.Max(calcedArea.CalcArea.Bottom, args.CalcArea.Bottom) - newCalcArea.Y; // Copy the new mandelnumbers together with the current mandelnumbers to a new array int[] tmpMandelNumbers = new int[newCalcArea.Width * newCalcArea.Height]; for(int x = 0; x < newCalcArea.Width; ++x) { for(int y = 0; y < newCalcArea.Height; ++y) { if(args.CalcArea.Contains(newCalcArea.X + x, newCalcArea.Y + y)) tmpMandelNumbers[x + y * newCalcArea.Width] = result[newCalcArea.X + x - args.CalcArea.X + (newCalcArea.Y + y - args.CalcArea.Y) * args.CalcArea.Width]; else tmpMandelNumbers[x + y * newCalcArea.Width] = mandelNumbers[newCalcArea.X + x - calcedArea.CalcArea.X + (newCalcArea.Y + y - calcedArea.CalcArea.Y) * calcedArea.CalcArea.Width]; } } // Store the new result mandelNumbers = tmpMandelNumbers; calcedArea.CalcArea = newCalcArea; // Fill the next gap, or render the bitmap (if we've filled all gaps) if(!calcPart()) renderBitmap(); } }
/// <summary>Sets the area of the mandelbrot that is to be shown (note that the CalcArea property is ignored)</summary> /// <param name="mea">The area of the mandelbrot that is to be shown</param> public void SetArea(MandelAreaArgs mea) { currArea = mea; currArea.CalcArea = new Rectangle(0, 0, currArea.PxWidth, currArea.PxHeight); currArea.CenterOnXAxis(); currArea.CenterOnYAxis(); Invalidate(); }
void OnMandelbrotDoneHandler(MandelAreaArgs args, int[] result) { deltaTicks = Environment.TickCount - startTicks; mandelNumbers = result; lastCalcW = args.PxWidth; lastCalcH = args.PxHeight; Invalidate(); }