Last active
May 13, 2019 23:41
-
-
Save robertmryan/91536bf75e46cbdaed92c37e99fdbe7d to your computer and use it in GitHub Desktop.
A few unrelated observations for http://stackoverflow.com/questions/39948082/long-cycle-blocks-application/39949292?noredirect=1#comment67183460_39949292
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
// 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See https://github.com/robertmryan/Mandelbrot for sample macOS/iOS app.