Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Last active May 13, 2019 23:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertmryan/91536bf75e46cbdaed92c37e99fdbe7d to your computer and use it in GitHub Desktop.
Save robertmryan/91536bf75e46cbdaed92c37e99fdbe7d to your computer and use it in GitHub Desktop.
// I'd define a Swift struct that captures all of the needed `Complex` arithmetic:
/// Complex number
struct Complex {
let real: Double
let imaginary: Double
init(real: Double, imaginary: Double) {
self.real = real
self.imaginary = imaginary
}
init(_ value: Int) {
self.init(real: Double(value), imaginary: 0)
}
init(_ value: Double) {
self.init(real: value, imaginary: 0)
}
}
extension Complex {
static func + (lhs: Complex, rhs: Complex) -> Complex {
return Complex(real: lhs.real + rhs.real, imaginary: lhs.imaginary + rhs.imaginary)
}
static func * (lhs: Complex, rhs: Complex) -> Complex {
return Complex(real: lhs.real * rhs.real - rhs.imaginary * lhs.imaginary, imaginary: lhs.imaginary * rhs.real + lhs.real * rhs.imaginary)
}
func pow2() -> Complex {
return self * self
}
func abs() -> Double {
return hypot(real, imaginary)
}
}
// then the mandelbrot calculation is simplified into something more intuitive:
/// Calculate Mandelbrot value.
///
/// - parameter c: Initial `Complex` value.
///
/// - returns: The number of iterations for `z = z^2 + c` to escape `threshold`.
private func mandelbrotValue(for c: Complex) -> Int? {
var z = Complex(0)
var iteration = 0
repeat {
z = z.pow2() + c
iteration += 1
} while z.abs() <= threshold && iteration < maxIterations
return iteration >= maxIterations ? nil : iteration
}
// My final routine is as follows:
override func calculate() {
let scale: CGFloat = NSScreen.main()!.backingScaleFactor
let size = imageView.bounds.size
source.setEventHandler { [unowned self] in
self.pixelsProcessed += self.source.data
self.progressIndicator.doubleValue = Double(self.pixelsProcessed) / (Double(size.width * size.height * scale * scale))
}
source.resume()
let realMinimum = -2.0
let realMaximum = 1.0
let imaginaryRange = (realMaximum - realMinimum) * Double(view.bounds.height / view.bounds.width)
mandelbrot(size: size,
upperLeft: Complex(real: realMinimum, imaginary: imaginaryRange / 2),
lowerRight: Complex(real: realMaximum, imaginary: -imaginaryRange / 2),
scale: scale) { image in
self.imageView.image = image
}
}
/// Calculate all Mandelbrot values for image.
///
/// - parameter size: The size of the image (in points)
/// - parameter upperLeft: The `Complex` number representing the upper left corner of the image.
/// - parameter lowerRight: The `Complex` number representing the lower right corner of the image.
/// - parameter scale: The scale of the image (i.e. how many pixels per point). E.g. retina devices may have scale of `2`.
/// - parameter completion: Completion handler for returning the resulting image.
private func mandelbrot(size: NSSize, upperLeft: Complex, lowerRight: Complex, scale: CGFloat = NSScreen.main!.backingScaleFactor, completion: @escaping (NSImage?) -> Void) {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let columns = Int(size.width * scale)
let rows = Int(size.height * scale)
let context = CGContext(data: nil, width: columns, height: rows, bitsPerComponent: 8, bytesPerRow: columns * 4, space: colorSpace, bitmapInfo: RGBA32.bitmapInfo)!
guard let pixelBuffer = context.data?.bindMemory(to: RGBA32.self, capacity: columns * rows) else {
fatalError("Unable to bind memory")
}
DispatchQueue.global(qos: .utility).async {
DispatchQueue.concurrentPerform(iterations: rows) { row in
let imaginary = self.imaginary(for: row, of: rows, between: upperLeft, and: lowerRight)
for column in 0 ..< columns {
let real = self.real(for: column, of: columns, between: upperLeft, and: lowerRight)
let m = self.mandelbrotValue(for: Complex(real: real, imaginary: imaginary))
pixelBuffer[row * columns + column] = self.color(for: m)
self.source.add(data: 1)
}
}
guard let outputCGImage = context.makeImage() else {
fatalError("Unable to make image")
}
DispatchQueue.main.async {
let image = NSImage(cgImage: outputCGImage, size: size)
completion(image)
}
}
}
/// Determine color based upon Mandelbrot value.
///
/// You can use whatever algorithm you want for coloring the pixel.
///
/// - parameter mandelbrotValue: The number of iterations required to escape threshold in Mandelbrot calculation.
///
/// - returns: A `RGBA32` color.
private func color(for mandelbrotValue: Int?) -> RGBA32 {
guard let mandelbrotValue = mandelbrotValue else {
return RGBA32.blackColor
}
let v = UInt8(min(255.0, pow(Double(min(255, mandelbrotValue-1)), 1.7)))
return RGBA32(red: v, green: v, blue: 255, alpha: 255)
}
/// Get imaginary component.
///
/// - parameter row: Zero-based row number.
/// - parameter rows: Total number of rows in which the `row` falls.
/// - parameter upperLeft: A `Complex` number representing the upper left of the image.
/// - parameter lowerRight: A `Complex` number representing the lower right of the image.
///
/// - returns: A `Double` of the imaginary value corresponding this this `row`.
private func imaginary(for row: Int, of rows: Int, between upperLeft: Complex, and lowerRight: Complex) -> Double {
let rowPercent = Double(row) / Double(rows)
return upperLeft.imaginary + (lowerRight.imaginary - upperLeft.imaginary) * rowPercent
}
/// Get real component.
///
/// - parameter column: Zero-based column number.
/// - parameter columns: Total number of columns in which the `column` fall.
/// - parameter upperLeft: A `Complex` number representing the upper left of the image.
/// - parameter lowerRight: A `Complex` number representing the lower right of the image.
///
/// - returns: A `Complex` of the value corresponding to this column (and the previously
/// calculated imaginary component.
private func real(for column: Int, of columns: Int, between upperLeft: Complex, and lowerRight: Complex) -> Double {
let columnPercent = Double(column) / Double(columns)
return upperLeft.real + (lowerRight.real - upperLeft.real) * columnPercent
}
// With
/// Color in a pixel buffer.
///
/// This is a 8 bit per channel RGBA representation of a color.
struct RGBA32: Equatable {
private var color: UInt32
var red: UInt8 {
return UInt8((color >> 24) & 255)
}
var green: UInt8 {
return UInt8((color >> 16) & 255)
}
var blue: UInt8 {
return UInt8((color >> 8) & 255)
}
var alpha: UInt8 {
return UInt8((color >> 0) & 255)
}
init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
color = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
}
static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}
static let blackColor = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
}
@robertmryan
Copy link
Author

See https://github.com/robertmryan/Mandelbrot for sample macOS/iOS app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment