Skip to content

Instantly share code, notes, and snippets.

Created October 4, 2014 13:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/fee867e5d821ff0b69f0 to your computer and use it in GitHub Desktop.
Save anonymous/fee867e5d821ff0b69f0 to your computer and use it in GitHub Desktop.
A simple Mandelbrot image generator in Swift.
//============================================================================
//
// 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)
}
}
//============================================================================
//
// 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)
//============================================================================
//
// 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)
}
}
//============================================================================
//
// 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