I built one of these long ago, and it got lost when I reformatted my SSD (sad day) so here is the new version.
It is very multithreaded, spawning numCores - 1
threads to calculate n
chunks. When one thread finishes, another is spawned until all chunks have been assigned.
It's quite quick and accurate, though the imageSize
must be a ratio of 2:1
for width:height
.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mandelbrot_Generator
{
class Program
{
// To avoid the need for unnecessary multiplication (and the appearance of magic numbers), we'll make a constant `twoSquared`:
const float twoSquared = 2 * 2;
static void Main(string[] args)
{
// Wooo! Mandelbrots! I miss the old programme though. It was probably better.
// That said, we'll get this thing on CR so they can tell us how badly we fkd up.
// Currently an arbitrary number.
short maxIterations = 1000;
// Let us consider a `width` and `height` of the generated image.
Size imageSize = new Size(4096, 2048);
// Next, consider `xCenter` and `yCenter` which represent what pixel is `(0,0)` in the specified image size.
Point center = new Point(imageSize.Width / 2, imageSize.Height / 2);
// And we'll scale the size so the brot sits within [-2,2],
SizeF scaleSize = new SizeF(center.X / 2, center.Y);
// Setup the number of chunks to break into.
int numberOfChunks = 256;
int numberOfColours = 32;
int numberOfCores = Environment.ProcessorCount - 1;
Console.WriteLine("Creating Mandelbrot image of size ({0},{1}) and max iteration count of {2}, splitting the image into {3} sections across {4} cores.", imageSize.Width, imageSize.Height, maxIterations, numberOfChunks, numberOfCores);
Stopwatch sw = new Stopwatch();
sw.Start();
// Build our chunks.
List<Chunk> chunks = new List<Chunk>(numberOfChunks);
for (int i = 0; i < numberOfChunks; i++)
chunks.Add(new Chunk(new Point(0, imageSize.Height / numberOfChunks * i), new Point(imageSize.Width, imageSize.Height / numberOfChunks * (i + 1))));
// Create and assign tasks (as we can).
List<Task<Result>> tasks = new List<Task<Result>>();
while (chunks.Count > 0)
{
if (tasks.Where(x => x.Status != TaskStatus.RanToCompletion).ToList().Count < numberOfCores)
{
if (chunks.Count > 0)
{
Task<Result> getSection = GenerateSectionAsync(chunks[0], center, scaleSize, maxIterations);
chunks.Remove(chunks[0]);
tasks.Add(getSection);
}
}
System.Threading.Thread.Sleep(1);
}
Console.WriteLine("Last chunk assigned, waiting for results.");
// Create the main results
short[] results = new short[imageSize.Width * imageSize.Height];
// Make sure we finish our tasks and add them to our results.
while (tasks.Count > 0)
{
var finishedTasks = tasks.Where(x => x.Status == TaskStatus.RanToCompletion).ToList();
foreach (var finishedTask in finishedTasks)
{
Result result = finishedTask.Result;
for (int y = result.Chunk.Start.Y; y < result.Chunk.End.Y; y++)
{
for (int x = 0; x < imageSize.Width; x++)
{
results[y * imageSize.Width + x] = result.Data[(y - result.Chunk.Start.Y) * imageSize.Width + x];
}
}
tasks.Remove(finishedTask);
}
System.Threading.Thread.Sleep(1);
}
sw.Stop();
Console.WriteLine("Took {0}ms.", sw.ElapsedMilliseconds);
Console.WriteLine("Mandelbrot created, building image...");
// Create our colours.
Color[] colors = new Color[numberOfColours];
for (int i = 0; i < numberOfColours; i++)
colors[i] = Color.FromArgb(255, 0, 0, i * (256 / numberOfColours));
// Create our image.
using (Bitmap image = new Bitmap(imageSize.Width, imageSize.Height))
{
for (int y = 0; y < imageSize.Height; y++)
{
for (int x = 0; x < imageSize.Width; x++)
{
image.SetPixel(x, y, colors[results[y * imageSize.Width + x] / (int)(Math.Ceiling(maxIterations / (float)numberOfColours))]);
}
}
image.Save("test.png", System.Drawing.Imaging.ImageFormat.Png);
}
Console.WriteLine("Image built, press enter to quit...");
Console.ReadLine();
}
public static Task<Result> GenerateSectionAsync(Chunk chunk, Point center, SizeF scaleSize, short maxIterations)
{
return Task.Run(() =>
{
return GenerateSection(chunk, center, scaleSize, maxIterations);
});
}
public struct Chunk
{
public Point Start { get; }
public Point End { get; }
public Chunk(Point start, Point end)
{
Start = start;
End = end;
}
}
public class Result
{
public Chunk Chunk { get; }
public short[] Data { get; }
public Result(Chunk chunk, short[] data)
{
Chunk = chunk;
Data = data;
}
}
private static Result GenerateSection(Chunk chunk, Point center, SizeF scaleSize, short maxIterations)
{
int startRow = chunk.Start.Y;
int endRow = chunk.End.Y;
int startColumn = chunk.Start.X;
int endColumn = chunk.End.X;
int height = endRow - startRow;
int width = endColumn - startColumn;
short[] results = new short[height * width];
// We'll need all of these later.
int relativeRow = 0;
short iteration = 0;
float xTemp = 0;
PointF location0 = new PointF();
PointF location = new PointF();
float xSquared = 0;
float ySquared = 0;
float nY = 0;
for (int y = startRow; y < endRow; y++)
{
for (int x = startColumn; x < endColumn; x++)
{
// The formula for a mandelbrot is z = z^2 + c, basically. We must relate that in code.
location0 = new PointF((x - center.X) / scaleSize.Width, (y - center.Y) / scaleSize.Height);
location = new PointF(0, 0);
iteration = 0;
xSquared = location.X * location.X;
ySquared = location.Y * location.Y;
xTemp = 0;
while (xSquared + ySquared < twoSquared && iteration < maxIterations)
{
xTemp = xSquared - ySquared + location0.X;
nY = 2 * location.X * location.Y + location0.Y;
location = new PointF(xTemp, nY);
iteration++;
xSquared = location.X * location.X;
ySquared = location.Y * location.Y;
}
results[relativeRow * width + x] = iteration;
}
relativeRow++;
}
return new Result(chunk, results);
}
}
}
Any/all comments to speed/clean it up are well appreciated. Ignore the fact that both the extra class
and struct
are in the Program
class. In the real application they would be separate, but far too small for me to do so here.
parallel.for
whereas the other is more specific and centered around the actual Mandelbrot set. Po-tay-toe, po-tah-toe. \$\endgroup\$Chunk
doesn't have to be a row, it could be split on columns instead, or actual chunks in space. I just had it split into specific rows for ease of coding it. \$\endgroup\$