I have an array with many millions of elements (7201 x 7201 data points) where I am converting the data to a greyscale image.
var imagePixels = heights.map { terrainToColorScale($0.height, gradient: .Linear) }
let _ = writeImageToFile(imagePixels, height: latStrides, width: lonStrides, imgColorSpace: .BW, to: imageURL, as: .png)
This snippet of code takes about 11 seconds to complete (CPU=2.3Ghz 8-Core i9), but I would like to get better performance if possible. Code is currently running in a single thread.
Would simply splitting my heights
array into chunks (say 100 chunks) and running a TaskGroup with a task for each chunk get a decent improvement?
Or am I looking at getting into Metal and shaders (I know zero about Metal!!) to get a better result?
Just for interest, the typical image generated is...
(Image downsampled as would be too big for upload here.)
Update: Adding code associated with terrainToColorScale
Basically, for a linear conversion it will take the terrain height (typically 0...9000) and scale it to return a value between 0...255
I also have non-linear implementations (not shown below) that will show greater detail for datasets that are mostly low/high terrain elevations.
let terrainLowerCutOff: Double = 0.0 // Mean Sea Level
let terrainUpperCutOff: Double = 9000.0 // Value in meters, just higher that Everest
func terrainToColorScale(_ elev: Double, lowerCutOff: Double = terrainLowerCutOff, upperCutOff: Double = terrainUpperCutOff, gradient: ImageColorGradient = .Linear) -> UInt8 {
switch gradient {
case .Linear:
return linearColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
case .LinearInverse:
return linearInverseColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
case .CurveEmphasiseLows:
return reciprocalPowerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
case .CurveEmphasiseLowsInverse:
return reciprocalInversePowerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
case .CurveEmphasiseHighs:
return powerColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
case .CurveEmphasiseHighsInverse:
return powerInverseColorScale(elev, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff)
}
}
fileprivate func linearColorScale(_ value: Double, lowerCutOff: Double, upperCutOff: Double) -> UInt8 {
return UInt8( 255 * normaliseColorScale(value, lowerCutOff: lowerCutOff, upperCutOff: upperCutOff) )
}
fileprivate func normaliseColorScale(_ value: Double, lowerCutOff: Double, upperCutOff: Double) -> Double {
switch value {
case _ where value <= lowerCutOff :
return 0.0
case _ where value >= upperCutOff :
return 1.0
default :
return (value - lowerCutOff) / (upperCutOff - lowerCutOff)
}
}