Commit 4dadde2a authored by Kalle Kabell's avatar Kalle Kabell

Add appendix 8 + 9

parent ad2d1dca
......@@ -23,6 +23,8 @@
E32ABCD32273092400D74B7C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E32ABCD22273092400D74B7C /* Assets.xcassets */; };
E32ABCD62273092400D74B7C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E32ABCD42273092400D74B7C /* LaunchScreen.storyboard */; };
E32ABCDE22730AE400D74B7C /* Kernels.metal in Sources */ = {isa = PBXBuildFile; fileRef = E32ABCDD22730AE400D74B7C /* Kernels.metal */; };
E3355E44232102D00093589E /* Appendix8.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3355E43232102D00093589E /* Appendix8.swift */; };
E3355E46232133790093589E /* Appendix9.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3355E45232133790093589E /* Appendix9.swift */; };
E3C475F622AA763D00FCAF05 /* Task4.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3C475F522AA763D00FCAF05 /* Task4.swift */; };
E3D058A622B9430800118F2F /* Appendix3.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D058A522B9430800118F2F /* Appendix3.swift */; };
E3D058A822B9431C00118F2F /* Appendix4.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D058A722B9431C00118F2F /* Appendix4.swift */; };
......@@ -51,6 +53,8 @@
E32ABCD52273092400D74B7C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
E32ABCD72273092400D74B7C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E32ABCDD22730AE400D74B7C /* Kernels.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Kernels.metal; sourceTree = "<group>"; };
E3355E43232102D00093589E /* Appendix8.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appendix8.swift; sourceTree = "<group>"; };
E3355E45232133790093589E /* Appendix9.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appendix9.swift; sourceTree = "<group>"; };
E3C475F522AA763D00FCAF05 /* Task4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task4.swift; sourceTree = "<group>"; };
E3D058A522B9430800118F2F /* Appendix3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appendix3.swift; sourceTree = "<group>"; };
E3D058A722B9431C00118F2F /* Appendix4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appendix4.swift; sourceTree = "<group>"; };
......@@ -81,6 +85,8 @@
E3D058A922B945CF00118F2F /* Appendix5.swift */,
E3D058AD22B94ABC00118F2F /* Appendix6.swift */,
E3D058AB22B9490300118F2F /* Appendix7.swift */,
E3355E43232102D00093589E /* Appendix8.swift */,
E3355E45232133790093589E /* Appendix9.swift */,
);
path = Appendices;
sourceTree = "<group>";
......@@ -219,6 +225,7 @@
565C4CFD22798EBA00275692 /* Task5.swift in Sources */,
566774762278836C003F881E /* Task1.swift in Sources */,
E3D058A622B9430800118F2F /* Appendix3.swift in Sources */,
E3355E44232102D00093589E /* Appendix8.swift in Sources */,
5667747922788388003F881E /* Task2.swift in Sources */,
56AD533B2278668B005B1E87 /* EditorViewController.swift in Sources */,
E32ABCDE22730AE400D74B7C /* Kernels.metal in Sources */,
......@@ -232,6 +239,7 @@
56AD53392278513B005B1E87 /* BrowserViewController.swift in Sources */,
E3D6AE6D22B0F94A00ACADC5 /* Appendix1.swift in Sources */,
E32ABCCE2273092300D74B7C /* MainViewController.swift in Sources */,
E3355E46232133790093589E /* Appendix9.swift in Sources */,
E32ABCCC2273092300D74B7C /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
......
import CoreImage
let appendix8: Effect = { originalImage, depthMap in
return try? LineAvereageKernel.apply(
withExtent: originalImage.extent,
inputs: [originalImage],
arguments: [
"width": originalImage.extent.width
]
)
}
private class LineAvereageKernel: CIImageProcessorKernel {
override public class func process(with inputs: [CIImageProcessorInput]?, arguments: [String : Any]?, output: CIImageProcessorOutput) throws {
guard let input = inputs?.first else { return }
// Input
let inputRegion = input.region
let pixelCount = Int(inputRegion.width * inputRegion.height)
let valueCount = pixelCount * 4 // 4 values (red, green, blue, alpha) per pixel
// Bind input memory pointer to Float32 to make reading easier
let inAddress: UnsafePointer<Float32> = input.baseAddress.bindMemory(to: Float32.self, capacity: valueCount)
// Output
let outputRegion = output.region
let outputPixelCount = Int(outputRegion.width * outputRegion.height)
let outputValueCount = outputPixelCount * 4 // 4 values (red, green, blue, alpha) per pixel
// Bind output memory pointer to Float32 to make writing easier
let outAddress: UnsafeMutablePointer<Float32> = output.baseAddress.bindMemory(to: Float32.self, capacity: outputValueCount)
// Loop through each row of pixels in the output region
// We use DispatchQueue.concurrentPerform() instead of a regular for loop
// to process multiple rows simultaneously
DispatchQueue.concurrentPerform(iterations: Int(outputRegion.height)) { regionY in
let absoluteY = Int(outputRegion.minY) + regionY
let rowAddress = outAddress.advanced(by: regionY * Int(outputRegion.width) * 4)
// Tuple to hold sums of color channel values (red, green, blue)
var sums: (Float, Float, Float) = (0, 0, 0)
for absoluteX in 0..<Int(outputRegion.minX + outputRegion.width) {
// Current point relative to original input image
let absolutePoint = CGPoint(x: absoluteX, y: absoluteY)
// Current point relative to the output region
let outputRegionRelativePoint = CGPoint(
x: absolutePoint.x - outputRegion.minX,
y: absolutePoint.y - outputRegion.minY
)
// Current point relative to the input region
let inputRegionRelativePoint = CGPoint(
x: outputRegionRelativePoint.x + outputRegion.minX - inputRegion.minX,
y: outputRegionRelativePoint.y + outputRegion.minY - inputRegion.minY
)
// Obtain pointer to input pixel
let inputPixelAddress = inAddress.advanced(by: Int(inputRegionRelativePoint.y * inputRegion.width + inputRegionRelativePoint.x) * 4)
// Read color channel values and add them to respective sums
sums.0 += inputPixelAddress[0] // red
sums.1 += inputPixelAddress[1] // green
sums.2 += inputPixelAddress[2] // blue
if outputRegion.contains(absolutePoint) {
// Obtain pointer to output pixel
let outputPixelAddress = rowAddress.advanced(by: Int(outputRegionRelativePoint.x * 4))
// Calculate average color values and write them to output
let divisor = Float(absoluteX + 1)
outputPixelAddress[0] = sums.0 / divisor
outputPixelAddress[1] = sums.1 / divisor
outputPixelAddress[2] = sums.2 / divisor
outputPixelAddress[3] = 1
}
}
}
}
override public class func formatForInput(at input: Int32) -> CIFormat {
// Request color values to be of type Float32, to make it easier to read and write values
return .RGBAf
}
override public class var outputFormat: CIFormat {
// Request color values to be of type Float32, to make it easier to read and write values
return .RGBAf
}
override public class func roi(forInput input: Int32, arguments: [String : Any]?, outputRect: CGRect) -> CGRect {
guard let width = arguments?["width"] as? CGFloat else { return outputRect }
// Use the orignal image width together with the output region to specify the region of interest.
// This ensures that the `process` method is given full rows of input pixels instead of only the pixels
// that lie within the output region
return CGRect(
x: 0,
y: outputRect.minY,
width: width,
height: outputRect.height
)
}
}
import CoreImage
import GameplayKit
let appendix9: Effect = { originalImage, depthMap in
return try? StereogramKernel.apply(
withExtent: depthMap.extent,
inputs: [depthMap],
arguments: [
"width": depthMap.extent.width
]
)
}
fileprivate typealias Float32Pointer = UnsafePointer<Float32>
fileprivate typealias Float32MutablePointer = UnsafeMutablePointer<Float32>
public class StereogramKernel: CIImageProcessorKernel {
static let colors: [CIColor] = [
.black,
.white,
.red,
.blue,
.green
]
override public class func process(with inputs: [CIImageProcessorInput]?, arguments: [String : Any]?, output: CIImageProcessorOutput) throws {
guard let input = inputs?.first else {
return
}
let depthOfField = arguments?["depthOfField"] as? Float ?? (1 / 3)
let dpi = arguments?["dpi"] as? Float ?? 72
let inputRegion = input.region
let pixelCount = Int(inputRegion.width * inputRegion.height)
let valueCount = pixelCount * 4
let inAddress: Float32Pointer = input.baseAddress.bindMemory(to: Float32.self, capacity: valueCount)
let tempOutAdress: Float32MutablePointer = Float32MutablePointer.allocate(capacity: valueCount)
let outputRegion = output.region
let outputPixelCount = Int(outputRegion.width * outputRegion.height)
let outputValueCount = outputPixelCount * 4
let outAddress: Float32MutablePointer = output.baseAddress.bindMemory(to: Float32.self, capacity: outputValueCount)
let eyeSeperation = (2.5 * dpi).rounded()
DispatchQueue.concurrentPerform(iterations: Int(inputRegion.height)) { rowIndex in
let randomIntGenerator = GKMersenneTwisterRandomSource(seed: UInt64(rowIndex + Int(inputRegion.minY)))
let row = inAddress.advanced(by: rowIndex * Int(inputRegion.width) * 4)
var same: [Int] = []
for x in 0..<Int(inputRegion.width) {
same.append(x)
}
for x in 0..<Int(inputRegion.width) {
let pixel = row.advanced(by: x * 4)
let z: Float = pixel.r
let stereoSeperation = Int(
(
(1 - (depthOfField * z)) * eyeSeperation
/ (2 - (depthOfField * z))
).rounded()
)
var left = x - ((stereoSeperation + (stereoSeperation & rowIndex & 1)) / 2)
var right = left + stereoSeperation
if left >= 0 && right < Int(inputRegion.width) {
var t: Float = 1
var zt: Float = 0
var visible = false
repeat {
zt = z + 2 * (2 - depthOfField * z) * t / (depthOfField * eyeSeperation)
visible =
row.advanced(by: (x - Int(t)) * 4).r < zt &&
row.advanced(by: (x + Int(t)) * 4).r < zt
t += 1.0
} while visible && zt < 1
if visible {
var k = same[left]
while k != left && k != right {
if k < right {
left = k
} else {
left = right
right = k
}
k = same[left]
}
same[left] = right
}
}
}
let outputRow = tempOutAdress.advanced(by: rowIndex * Int(inputRegion.width) * 4)
for _x in 1...Int(inputRegion.width) {
let x = Int(inputRegion.width) - _x
var outPixel = outputRow.advanced(by: x * 4)
if same[x] == x {
let ran = abs(randomIntGenerator.nextInt()) % colors.count
let color = colors[ran]
outPixel.r = Float(color.red)
outPixel.g = Float(color.green)
outPixel.b = Float(color.blue)
outPixel.a = 1.0
} else {
let sameOffset = (rowIndex * Int(inputRegion.width) + same[x]) * 4
let samePixel = tempOutAdress.advanced(by: sameOffset)
outPixel.r = samePixel.r
outPixel.g = samePixel.g
outPixel.b = samePixel.b
outPixel.a = samePixel.a
}
}
}
for y in Int(outputRegion.minY)...Int(outputRegion.maxY) {
for x in Int(outputRegion.minX)...Int(outputRegion.maxX) {
let point = CGPoint(x: x, y: y)
if inputRegion.contains(point) {
let samePixel = tempOutAdress.advanced(by: offset(of: point, in: inputRegion) * 4)
var outPixel = outAddress.advanced(by: offset(of: point, in: outputRegion) * 4)
outPixel.r = samePixel.r
outPixel.g = samePixel.g
outPixel.b = samePixel.b
outPixel.a = samePixel.a
}
}
}
tempOutAdress.deallocate()
}
override public class func formatForInput(at input: Int32) -> CIFormat {
return CIFormat.RGBAf
}
override public class var outputFormat: CIFormat {
return CIFormat.RGBAf
}
override public class func roi(forInput input: Int32, arguments: [String : Any]?, outputRect: CGRect) -> CGRect {
guard let width = arguments?["width"] as? CGFloat else { return outputRect }
return CGRect(
x: 0,
y: outputRect.minY,
width: width,
height: outputRect.height
)
}
private class func offset(of point: CGPoint, in rect: CGRect) -> Int {
let relativeY = Int(point.y - rect.minY)
let relativeX = Int(point.x - rect.minX)
return relativeY * Int(rect.width) + relativeX
}
}
fileprivate extension Float32Pointer {
var r: Float32 { return self[0] }
var g: Float32 { return self[1] }
var b: Float32 { return self[2] }
var a: Float32 { return self[3] }
}
fileprivate extension Float32MutablePointer {
var r: Float32 {
get { return self[0] }
set { self[0] = newValue }
}
var g: Float32 {
get { return self[1] }
set { self[1] = newValue }
}
var b: Float32 {
get { return self[2] }
set { self[2] = newValue }
}
var a: Float32 {
get { return self[3] }
set { self[3] = newValue }
}
}
......@@ -21,7 +21,9 @@ class EditorViewController: UIViewController {
appendix4,
appendix5,
appendix6,
appendix7
appendix7,
appendix8,
appendix9
]
private var selectedEffect: Int = UserDefaults.standard.integer(forKey: "selectedEffect") {
didSet {
......
......@@ -200,6 +200,35 @@ float4 redAndBlue(sample_t input, sample_t depthMap) {
Take your new knowledge about Metal Shading Language and depth maps with you to `Task5.swift` and follow the instructions to play around with your own image kernels.
# Appendices
## Appendix 8 – CIImageProcessorKernel
When CIImage's built-in tools for image processing aren't enough, the `CIImageProcessorKernel` class is provided as an "escape hatch" allowing you to hook into the CIImage processing graph with any processing frameworks or method that you wish.
### The effect
The example in this appendix performs CPU-based rendering of a simple distortion effect.
For each row of pixels in the image, go through the pixels from left to right and set each pixel to the average color of the pixel encountered so far.
In other words, for each row:
- Pixel #1 will have its original color
- Pixel #2 will be the average color of pixels #1 and #2
- Pixel #3 will be the average color of pixels #1, #2 and #3
- ...and so on
### CIImageProcessorKernel
To use the CIImageProcessorKernel class, you subclass it and override a few methods, out of which the `process()` method is the most interesting. It receives 3 arguments: an array of inputs, a dictionary of arguments and an output.
The inputs and outputs are of type `CIImageProcessorInput` and `CIImageProcessorOutput`, respectively, and they contain meta data about the input and output images along with different methods of accessing and manipulating the image data (Metal textures, CoreVideo pixel buffers, IOSurface objects). For this appendix, we're interested in pointers to the "raw" memory address of the input data (for reading from) as well as the memory address for the output (for writing to).
These pointers are of type `UnsafeRawPointer` and `UnsafeMutableRawPointer` which we bind to `UnsafePointer<Float32>` and `UnsafeMutablePointer<Float32>` to make them easier to read from and write to.
## Appendix 9
# References
- [Core Image](https://developer.apple.com/documentation/coreimage) by Apple
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment