Created
October 3, 2014 22:19
-
-
Save anonymous/ce4b81474c5eecb8d8ad to your computer and use it in GitHub Desktop.
A simple mandelbrot generator in Swift.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//============================================================================ | |
// | |
// mandelbrot.swift | |
// | |
// Default settings are: | |
// cx: -0.74552972800463340 | |
// cy: 0.08245763776447299 | |
// numRows: 135 | |
// numColumns: 256 (same aspect ratio as 4096 x 2160) | |
// zoom: 10_000_000_000 | |
// maxIters: 5_000_000 | |
// | |
// All setting must be set by editing the code (near the bottom) except | |
// numberOfRows which can be set by a command line argument. | |
// Computation is done by async dispatching each row to GCD. | |
// | |
// Compile: | |
// | |
// $ xcrun swiftc -O mandelbrot.swift | |
// | |
// | |
// Run (and measure time): | |
// | |
// $ time ./mandelbrot 810 | |
// Generating image of size: 1536 x 810 ... | |
// Rows done: 10 / 810 | |
// Rows done: 20 / 810 | |
// ... | |
// Rows done: 810 / 810 | |
// Total time: 13m9.264s | |
// Iterating time: 13m9.026s | |
// Draw&save time: 0m0.238s | |
// | |
// real 13m9.257s | |
// user 103m47.187s | |
// sys 0m3.623s | |
// | |
// This was on my MBP late 2013, 2 GHz i7, 8GB 1600 MHz DDR3, | |
// Xcode 6.1 Beta 3 (6A1042b), OS X 10.9.5 (13F34). | |
// | |
// As can be seen above, most time is of course spent in the innermost | |
// iteration loop. | |
//============================================================================ | |
import Cocoa | |
//---------------------------------------------------------------------------- | |
// Image structs: | |
//---------------------------------------------------------------------------- | |
struct Rgba { | |
let (r, g, b, a) = (UInt8(0), UInt8(0), UInt8(0), UInt8(255)) | |
init(_ r: UInt8, _ g: UInt8, _ b: UInt8, _ a: UInt8) { | |
(self.r, self.g, self.b, self.a) = (r, g, b, a) | |
} | |
} | |
struct Image { | |
let w: Int, h: Int | |
var pixels: [Rgba] | |
init(_ w: Int, _ h: Int) { | |
(self.w, self.h) = (w, h) | |
pixels = [Rgba](count: w * h, repeatedValue: Rgba(0, 0, 0, 255)) | |
} | |
mutating func setPixelAtX(x: Int, y: Int, toR r: UInt8, g: UInt8, b: UInt8, a: UInt8) { | |
pixels[x + y * w] = Rgba(r, g, b, a) | |
} | |
func saveImageAs(filename: String) -> Bool { | |
var bitmapPlanes = [UnsafeMutablePointer<UInt8>(pixels)] | |
var bmir: NSBitmapImageRep = NSBitmapImageRep( | |
bitmapDataPlanes: &bitmapPlanes, pixelsWide: w, pixelsHigh: h, | |
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, | |
colorSpaceName: NSDeviceRGBColorSpace, | |
bitmapFormat: NSBitmapFormat.NS32BitLittleEndianBitmapFormat | NSBitmapFormat.NSAlphaNonpremultipliedBitmapFormat, | |
bytesPerRow: w * 4, bitsPerPixel: 32)! | |
var pngData: NSData = bmir.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [:])! | |
return pngData.writeToFile(filename, atomically: true) | |
} | |
} | |
//---------------------------------------------------------------------------- | |
// Mandelbrot struct: | |
//---------------------------------------------------------------------------- | |
struct Mandelbrot { | |
let (cx, cy): (Double, Double) | |
let zoom: Double | |
let maxIters: Int | |
let (pw, ph): (Int, Int) | |
var iterMap: [Int] | |
let iteratingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) | |
let iteratingGroup = dispatch_group_create() | |
let printQueue = dispatch_queue_create("com.sloppyfocus.mandelbrot.printqueue", DISPATCH_QUEUE_SERIAL) | |
let printGroup = dispatch_group_create() | |
init(cx: Double, cy: Double, zoom: Double, maxIter: Int, pw: Int, ph: Int) { | |
(self.cx, self.cy) = (cx, cy) | |
self.zoom = zoom | |
self.maxIters = maxIter | |
(self.pw, self.ph) = (pw, ph) | |
iterMap = [Int](count: pw * ph, repeatedValue: 0) | |
} | |
mutating func computeRowRange(rowsRange: Range<Int>, imp: UnsafeMutablePointer<Int>) { | |
var iter: Int | |
var (zr, zi, zrsqr, zisqr): (Double, Double, Double, Double) | |
var (x, y): (Double, Double) | |
let hpw: Double = Double(pw) * 0.5 | |
let hph: Double = Double(ph) * 0.5 | |
let scale = M_PI / (sqrt(hpw*hpw + hph*hph) * zoom) | |
for py in rowsRange { | |
y = (Double(py) - hph) * scale + cy | |
for px in 0 ..< pw { | |
x = (Double(px) - hpw) * scale + cx | |
(iter, zr, zi, zrsqr, zisqr) = (0, 0.0, 0.0, 0.0, 0.0) | |
// This while-loop is what matters, and after a bit of | |
// experimentation, I concluded that this is at least | |
// faster than a lot of other ways to implement it: | |
while (zrsqr + zisqr < 4.0 && iter < maxIters) { | |
zi = 2.0 * zr * zi + y | |
zr = zrsqr - zisqr + x | |
zrsqr = zr * zr | |
zisqr = zi * zi | |
iter++ | |
} | |
imp[px + py*pw] = iter | |
} | |
} | |
} | |
mutating func compute() { | |
let numDispatches = ph | |
let rowsPerDispatch = ph / numDispatches | |
var imp = UnsafeMutablePointer<Int>(self.iterMap) | |
var numRowsDone = 0 | |
for i in 0 ..< numDispatches { | |
dispatch_group_async(iteratingGroup, iteratingQueue, { () -> Void in | |
let rowsRange = (rowsPerDispatch * i) ..< (rowsPerDispatch * (i + 1)) | |
self.computeRowRange(rowsRange, imp: imp) | |
dispatch_group_async(self.printGroup, self.printQueue) { () -> Void in | |
numRowsDone++ | |
if numRowsDone % 10 == 0 { | |
println("Rows done: \(numRowsDone) / \(self.ph)") | |
} | |
} | |
}) | |
} | |
dispatch_group_wait(printGroup, DISPATCH_TIME_FOREVER) | |
dispatch_group_wait(iteratingGroup, DISPATCH_TIME_FOREVER) | |
} | |
func drawAndSaveAs(filename: String) -> Bool { | |
var v: Double, r: Double, g: Double, b: Double | |
var img = Image(pw, ph) | |
var iters: Int, itersL = Int.max, itersH = Int.min | |
for i in 0 ..< iterMap.count { | |
iters = iterMap[i] | |
(itersL, itersH) = (min(iters, itersL), max(iters, itersH)) | |
} | |
for py in 0 ..< ph { | |
for px in 0 ..< pw { | |
iters = iterMap[px + py*pw] | |
v = Double(iters - itersL) / Double(itersH - itersL) | |
if iters == maxIters { v = 0.0 } | |
(r, g, b) = (pow(v, 0.25) * 255.0, pow(v, 0.15) * 255.0, pow(v, 0.4) * 255.0) | |
img.setPixelAtX(px, y: py, toR: UInt8(r), g: UInt8(g), b: UInt8(b), a: 255) | |
} | |
} | |
return img.saveImageAs(filename) | |
} | |
} | |
//---------------------------------------------------------------------------- | |
// Helper functions to measure and report time: | |
//---------------------------------------------------------------------------- | |
func time(fn: () -> Void) -> Double { | |
let start = CACurrentMediaTime(); fn(); let stop = CACurrentMediaTime() | |
return stop - start | |
} | |
func printTime(label: String, seconds: Double) { | |
let minutes: Int = Int(floor(seconds / 60.0)) | |
println(String(format: "\(label) \(minutes)m%.3fs", seconds - Double(minutes) * 60.0)) | |
} | |
//---------------------------------------------------------------------------- | |
// Render: | |
//---------------------------------------------------------------------------- | |
// Default number of rows: | |
var numRows: Int = 135 | |
// If number of rows is given by cmd line arg: | |
if Process.arguments.count == 2 { | |
if let rows = Process.arguments[1].toInt() { | |
if numRows < 1 || numRows > 100_000 { println("Illegal number of rows! Defaults to 135."); } | |
else { numRows = rows } | |
} else { | |
println("Usage: mandelbrot [numRows]"); exit(0) | |
} | |
} | |
// Width of image is set according to aspect ratio of 4096 2160: | |
let numColumns: Int = Int(round(Double(numRows) * (4096.0 / 2160.0))) | |
println("Generating image of size: \(numColumns) x \(numRows) ...") | |
// Set center, deep zoom, max iterations to 5 million and pixel width & height: | |
var m = Mandelbrot( | |
cx: -0.74552972800463340, cy: 0.08245763776447299, | |
zoom: 10_000_000_000, | |
maxIter: 5_000_000, | |
pw: numColumns, ph: numRows) | |
// Compute iterations and save image, measure the time of iterating and drawing+saving: | |
var (iterTime, drawTime): (Double, Double) | |
iterTime = time { m.compute() } | |
drawTime = time { | |
if m.drawAndSaveAs("~/mandelbrot_test.png".stringByExpandingTildeInPath) == false { | |
println("ERROR: Image could not be saved!") | |
} | |
} | |
printTime("Total time:", iterTime + drawTime) | |
printTime("Iterating time:", iterTime) | |
printTime("Draw&save time:", drawTime) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment