Created
October 4, 2014 13:05
-
-
Save anonymous/fee867e5d821ff0b69f0 to your computer and use it in GitHub Desktop.
A simple Mandelbrot image 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
//============================================================================ | |
// | |
// Image.swift | |
// | |
//============================================================================ | |
import Cocoa | |
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) | |
} | |
} | |
class 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)) | |
} | |
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) | |
} | |
} |
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
//============================================================================ | |
// | |
// main.swift | |
// | |
//============================================================================ | |
// | |
// Requires: Mandelbrot.swift, MandelbrotIterator.swift and Image.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 Image.swift MandelbrotIterator.swift Mandelbrot.swift main.swift -o mandelbrot | |
// | |
// | |
// Run (and measure time, generating and image of size 4096 x 2160): | |
// | |
// $ time ./mandelbrot 2160 | |
// Generating image of size: 4096 x 2160 ... | |
// Rows done: 10 / 2160 | |
// Rows done: 20 / 2160 | |
// ... | |
// Rows done: 2160 / 2160 | |
// Total time: 92m14.764s | |
// Iterating time: 92m12.534s | |
// Draw&save time: 0m2.229s | |
// | |
// real 92m14.614s | |
// user 736m2.658s | |
// sys 0m17.462s | |
// | |
// 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, in MandelbrotIterator.swift. | |
//============================================================================ | |
import Cocoa | |
//---------------------------------------------------------------------------- | |
// 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, | |
maxIters: 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) |
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 | |
// | |
//============================================================================ | |
import Foundation | |
class Mandelbrot { | |
let (cx, cy): (Double, Double) | |
let zoom: Double | |
let (pw, ph): (Int, Int) | |
var iterMap: [Int] | |
var maxIters: 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, maxIters: Int, pw: Int, ph: Int) { | |
(self.cx, self.cy) = (cx, cy) | |
self.zoom = zoom | |
self.maxIters = maxIters | |
(self.pw, self.ph) = (pw, ph) | |
iterMap = [Int](count: pw * ph, repeatedValue: 0) | |
} | |
func computeRowRange(rowsRange: Range<Int>, imp: UnsafeMutablePointer<Int>) { | |
// NOTE: As this function will be async dispatched, it must not update | |
// any state that is shared among different dispatches. | |
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) | |
let iterator = MandelbrotIterator(maxIters) | |
for py in rowsRange { | |
y = (Double(py) - hph) * scale + cy | |
for px in 0 ..< pw { | |
x = (Double(px) - hpw) * scale + cx | |
imp[px + py*pw] = iterator.numIterationsForX(x, y: y) | |
} | |
} | |
} | |
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) | |
} | |
} |
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
//============================================================================ | |
// | |
// MandelbrotIterator.swift | |
// | |
//============================================================================ | |
class MandelbrotIterator { | |
let maxIters: Int | |
init(_ maxIters: Int) { | |
self.maxIters = maxIters | |
} | |
func numIterationsForX(x: Double, y: Double) -> Int { | |
var (iter, zr, zi, zrsqr, zisqr) = (0, 0.0, 0.0, 0.0, 0.0) | |
while (zrsqr + zisqr < 4.0 && iter < maxIters) { | |
zi = 2.0 * zr * zi + y | |
zr = zrsqr - zisqr + x | |
zrsqr = zr * zr | |
zisqr = zi * zi | |
iter++ | |
} | |
return iter | |
} | |
} | |
/* | |
// --------------------------------------------------------------------------- | |
// An example of how to decrease performance of a heavily used class: | |
// --------------------------------------------------------------------------- | |
// | |
// Note: These conclusions are made using Xcode 6.1 6A1042b (GM Seed/Beta 3). | |
// | |
// Using the following implementation is 20 times slower than using the above. | |
// | |
// The only change is that iter is now a property instead of a local var. | |
// | |
// This is because Swift currently can not optimize cross-file property writes | |
// of classes better than that. | |
// | |
// So, if the MandelbrotIterator class definition is cut & pasted into | |
// Mandelbrot.swift (ie the same file as where it is used), this | |
// implementation will be as fast as the one above. | |
// --------------------------------------------------------------------------- | |
class MandelbrotIterator { | |
let maxIters: Int | |
var iter: Int = 0 | |
init(_ maxIters: Int) { | |
self.maxIters = maxIters | |
} | |
func numIterationsForX(x: Double, y: Double) -> Int { | |
var (zr, zi, zrsqr, zisqr) = (0.0, 0.0, 0.0, 0.0) | |
iter = 0 | |
while (zrsqr + zisqr < 4.0 && iter < maxIters) { | |
zi = 2.0 * zr * zi + y | |
zr = zrsqr - zisqr + x | |
zrsqr = zr * zr | |
zisqr = zi * zi | |
iter++ | |
} | |
return iter | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment