From 4a436e00baa4b4771a9633532e36685f3145d69b Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:05:05 +0200 Subject: [PATCH 1/7] Adds .gitignore and clears cached files --- .gitignore | 6 ++ .../contents.xcworkspacedata | 7 -- .../UserInterfaceState.xcuserstate | Bin 52700 -> 0 bytes .../xcschemes/ParticleCam.xcscheme | 92 ------------------ .../xcschemes/xcschememanagement.plist | 22 ----- 5 files changed, 6 insertions(+), 121 deletions(-) create mode 100644 .gitignore delete mode 100644 ParticleCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 ParticleCam.xcodeproj/project.xcworkspace/xcuserdata/simongladman.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/ParticleCam.xcscheme delete mode 100644 ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc25fe4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store + +# Xcode +# this ignores dummy workspace files in xcodeproj dir +ParticleCam.xcodeproj/* +!ParticleCam.xcodeproj/project.pbxproj diff --git a/ParticleCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ParticleCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a08862e..0000000 --- a/ParticleCam.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/ParticleCam.xcodeproj/project.xcworkspace/xcuserdata/simongladman.xcuserdatad/UserInterfaceState.xcuserstate b/ParticleCam.xcodeproj/project.xcworkspace/xcuserdata/simongladman.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 564866f13840e6fd76f2f052a86244f101ca7c00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52700 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 diff --git a/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/ParticleCam.xcscheme b/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/ParticleCam.xcscheme deleted file mode 100644 index 3703cc7..0000000 --- a/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/ParticleCam.xcscheme +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist b/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 15756a3..0000000 --- a/ParticleCam.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - ParticleCam.xcscheme - - orderHint - 0 - - - SuppressBuildableAutocreation - - 3EA9309A1B75BFFA0067276D - - primary - - - - - From 10a2dc121a8f69b2ae5d393c47f290d793975a18 Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:09:04 +0200 Subject: [PATCH 2/7] Updates project from Swift 2 to Swift 3 syntax --- ParticleCam.xcodeproj/project.pbxproj | 12 +++- ParticleCam/AppDelegate.swift | 12 ++-- ParticleCam/ParticleLab.swift | 26 +++---- ParticleCam/ViewController.swift | 4 +- ParticleCam/classes/CameraCaptureHelper.swift | 16 ++--- ParticleCam/classes/ImageView.swift | 48 ++++++------- ParticleCam/classes/MetalFilter.swift | 70 +++++++++---------- 7 files changed, 99 insertions(+), 89 deletions(-) diff --git a/ParticleCam.xcodeproj/project.pbxproj b/ParticleCam.xcodeproj/project.pbxproj index 5fc1472..c727fd4 100644 --- a/ParticleCam.xcodeproj/project.pbxproj +++ b/ParticleCam.xcodeproj/project.pbxproj @@ -126,7 +126,9 @@ TargetAttributes = { 3EA9309A1B75BFFA0067276D = { CreatedOnToolsVersion = 7.0; - DevelopmentTeam = ZBFYF9JG5V; + DevelopmentTeam = 6CFA54MQCP; + LastSwiftMigration = 0830; + ProvisioningStyle = Manual; }; }; }; @@ -287,11 +289,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = 6CFA54MQCP; INFOPLIST_FILE = ParticleCam/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.ParticleCam; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = "(dan jan 16) Dev Any App"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -302,11 +308,15 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = ParticleCam/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.ParticleCam; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/ParticleCam/AppDelegate.swift b/ParticleCam/AppDelegate.swift index 7693892..bebad36 100644 --- a/ParticleCam/AppDelegate.swift +++ b/ParticleCam/AppDelegate.swift @@ -14,30 +14,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } - func applicationWillResignActive(application: UIApplication) { + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - func applicationDidEnterBackground(application: UIApplication) { + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - func applicationWillTerminate(application: UIApplication) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/ParticleCam/ParticleLab.swift b/ParticleCam/ParticleLab.swift index e392c2e..9b5b1f1 100644 --- a/ParticleCam/ParticleLab.swift +++ b/ParticleCam/ParticleLab.swift @@ -25,12 +25,12 @@ import MetalPerformanceShaders class ParticleCamFilter: MetalImageFilter { - let particleCount = ParticleCount.OneMillion.rawValue + let particleCount = ParticleCount.oneMillion.rawValue let alignment:Int = 0x4000 let particlesMemoryByteSize:Int - var particlesMemory:UnsafeMutablePointer = nil - let particlesVoidPtr: COpaquePointer + var particlesMemory:UnsafeMutableRawPointer? = nil + let particlesVoidPtr: OpaquePointer let particlesParticlePtr: UnsafeMutablePointer let particlesParticleBufferPtr: UnsafeMutableBufferPointer @@ -38,23 +38,23 @@ class ParticleCamFilter: MetalImageFilter { [unowned self] in - return self.device.newBufferWithBytesNoCopy(self.particlesMemory, + return self.device.makeBuffer(bytesNoCopy: self.particlesMemory!, length: Int(self.particlesMemoryByteSize), - options: .CPUCacheModeDefaultCache, + options: MTLResourceOptions(), deallocator: nil) }() - let particleSize = sizeof(Particle) + let particleSize = MemoryLayout.size // MARK: Initialisation init() { - particlesMemoryByteSize = particleCount * sizeof(Particle) + particlesMemoryByteSize = particleCount * MemoryLayout.size posix_memalign(&particlesMemory, alignment, particlesMemoryByteSize) - particlesVoidPtr = COpaquePointer(particlesMemory) + particlesVoidPtr = OpaquePointer(particlesMemory!) particlesParticlePtr = UnsafeMutablePointer(particlesVoidPtr) particlesParticleBufferPtr = UnsafeMutableBufferPointer(start: particlesParticlePtr, count: particleCount) @@ -109,11 +109,11 @@ class ParticleCamFilter: MetalImageFilter enum ParticleCount: Int { - case QuarterMillion = 262144 - case HalfMillion = 524288 - case OneMillion = 1048576 - case TwoMillion = 2097152 - case FourMillion = 4194304 + case quarterMillion = 262144 + case halfMillion = 524288 + case oneMillion = 1048576 + case twoMillion = 2097152 + case fourMillion = 4194304 } // Particles use x and y for position and z and w for velocity diff --git a/ParticleCam/ViewController.swift b/ParticleCam/ViewController.swift index cc06395..0be3cae 100644 --- a/ParticleCam/ViewController.swift +++ b/ParticleCam/ViewController.swift @@ -12,7 +12,7 @@ class ViewController: UIViewController, CameraCaptureHelperDelegate { let imageView = MetalImageView() - let cameraCaptureHelper = CameraCaptureHelper(cameraPosition: .Front) + let cameraCaptureHelper = CameraCaptureHelper(cameraPosition: .front) let particleCamFilter = ParticleCamFilter() @@ -36,7 +36,7 @@ class ViewController: UIViewController, CameraCaptureHelperDelegate } - func newCameraImage(cameraCaptureHelper: CameraCaptureHelper, image: CIImage) + func newCameraImage(_ cameraCaptureHelper: CameraCaptureHelper, image: CIImage) { particleCamFilter.inputImage = image diff --git a/ParticleCam/classes/CameraCaptureHelper.swift b/ParticleCam/classes/CameraCaptureHelper.swift index cdb1b4e..a20e0b9 100644 --- a/ParticleCam/classes/CameraCaptureHelper.swift +++ b/ParticleCam/classes/CameraCaptureHelper.swift @@ -33,11 +33,11 @@ class CameraCaptureHelper: NSObject initialiseCaptureSession() } - private func initialiseCaptureSession() + fileprivate func initialiseCaptureSession() { captureSession.sessionPreset = AVCaptureSessionPresetiFrame1280x720 - guard let camera = (AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as! [AVCaptureDevice]) + guard let camera = (AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as! [AVCaptureDevice]) .filter({ $0.position == cameraPosition }) .first else { @@ -58,7 +58,7 @@ class CameraCaptureHelper: NSObject let videoOutput = AVCaptureVideoDataOutput() videoOutput.setSampleBufferDelegate(self, - queue: dispatch_queue_create("sample buffer delegate", DISPATCH_QUEUE_SERIAL)) + queue: DispatchQueue(label: "sample buffer delegate", attributes: [])) if captureSession.canAddOutput(videoOutput) { @@ -71,19 +71,19 @@ class CameraCaptureHelper: NSObject extension CameraCaptureHelper: AVCaptureVideoDataOutputSampleBufferDelegate { - func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!) + func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { - connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.sharedApplication().statusBarOrientation.rawValue)! + connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } - dispatch_async(dispatch_get_main_queue()) + DispatchQueue.main.async { self.delegate?.newCameraImage(self, - image: CIImage(CVPixelBuffer: pixelBuffer)) + image: CIImage(cvPixelBuffer: pixelBuffer)) } } @@ -91,5 +91,5 @@ extension CameraCaptureHelper: AVCaptureVideoDataOutputSampleBufferDelegate protocol CameraCaptureHelperDelegate: class { - func newCameraImage(cameraCaptureHelper: CameraCaptureHelper, image: CIImage) + func newCameraImage(_ cameraCaptureHelper: CameraCaptureHelper, image: CIImage) } diff --git a/ParticleCam/classes/ImageView.swift b/ParticleCam/classes/ImageView.swift index eb0fcdb..8aa4a2f 100644 --- a/ParticleCam/classes/ImageView.swift +++ b/ParticleCam/classes/ImageView.swift @@ -16,20 +16,20 @@ import MetalKit class MetalImageView: MTKView { - let colorSpace = CGColorSpaceCreateDeviceRGB()! + let colorSpace = CGColorSpaceCreateDeviceRGB() lazy var commandQueue: MTLCommandQueue = { [unowned self] in - return self.device!.newCommandQueue() + return self.device!.makeCommandQueue() }() lazy var ciContext: CIContext = { [unowned self] in - return CIContext(MTLDevice: self.device!) + return CIContext(mtlDevice: self.device!) }() override init(frame frameRect: CGRect, device: MTLDevice?) @@ -63,14 +63,14 @@ class MetalImageView: MTKView { guard let image = image, - targetTexture = currentDrawable?.texture else + let targetTexture = currentDrawable?.texture else { return } - let commandBuffer = commandQueue.commandBuffer() + let commandBuffer = commandQueue.makeCommandBuffer() - let bounds = CGRect(origin: CGPointZero, size: drawableSize) + let bounds = CGRect(origin: CGPoint.zero, size: drawableSize) let originX = image.extent.origin.x let originY = image.extent.origin.y @@ -80,16 +80,16 @@ class MetalImageView: MTKView let scale = min(scaleX, scaleY) let scaledImage = image - .imageByApplyingTransform(CGAffineTransformMakeTranslation(-originX, -originY)) - .imageByApplyingTransform(CGAffineTransformMakeScale(scale, scale)) + .applying(CGAffineTransform(translationX: -originX, y: -originY)) + .applying(CGAffineTransform(scaleX: scale, y: scale)) ciContext.render(scaledImage, - toMTLTexture: targetTexture, + to: targetTexture, commandBuffer: commandBuffer, bounds: bounds, colorSpace: colorSpace) - commandBuffer.presentDrawable(currentDrawable!) + commandBuffer.present(currentDrawable!) commandBuffer.commit() } @@ -105,21 +105,21 @@ class MetalImageView: MTKView class OpenGLImageView: GLKView { - let eaglContext = EAGLContext(API: .OpenGLES2) + let eaglContext = EAGLContext(api: .openGLES2) lazy var ciContext: CIContext = { [unowned self] in - return CIContext(EAGLContext: self.eaglContext, + return CIContext(eaglContext: self.eaglContext!, options: [kCIContextWorkingColorSpace: NSNull()]) }() override init(frame: CGRect) { - super.init(frame: frame, context: eaglContext) + super.init(frame: frame, context: eaglContext!) - context = self.eaglContext + context = self.eaglContext! delegate = self } @@ -145,7 +145,7 @@ class OpenGLImageView: GLKView extension OpenGLImageView: GLKViewDelegate { - func glkView(view: GLKView, drawInRect rect: CGRect) + func glkView(_ view: GLKView, drawIn rect: CGRect) { guard let image = image else { @@ -153,32 +153,32 @@ extension OpenGLImageView: GLKViewDelegate } let targetRect = image.extent.aspectFitInRect( - target: CGRect(origin: CGPointZero, + target: CGRect(origin: CGPoint.zero, size: CGSize(width: drawableWidth, height: drawableHeight))) let ciBackgroundColor = CIColor( - color: backgroundColor ?? UIColor.whiteColor()) + color: backgroundColor ?? UIColor.white) - ciContext.drawImage(CIImage(color: ciBackgroundColor), - inRect: CGRect(x: 0, + ciContext.draw(CIImage(color: ciBackgroundColor), + in: CGRect(x: 0, y: 0, width: drawableWidth, height: drawableHeight), - fromRect: CGRect(x: 0, + from: CGRect(x: 0, y: 0, width: drawableWidth, height: drawableHeight)) - ciContext.drawImage(image, - inRect: targetRect, - fromRect: image.extent) + ciContext.draw(image, + in: targetRect, + from: image.extent) } } extension CGRect { - func aspectFitInRect(target target: CGRect) -> CGRect + func aspectFitInRect(target: CGRect) -> CGRect { let scale: CGFloat = { diff --git a/ParticleCam/classes/MetalFilter.swift b/ParticleCam/classes/MetalFilter.swift index 0632d79..0c26545 100644 --- a/ParticleCam/classes/MetalFilter.swift +++ b/ParticleCam/classes/MetalFilter.swift @@ -19,20 +19,20 @@ import CoreImage class MetalFilter: CIFilter { let device: MTLDevice = MTLCreateSystemDefaultDevice()! - let colorSpace = CGColorSpaceCreateDeviceRGB()! + let colorSpace = CGColorSpaceCreateDeviceRGB() lazy var ciContext: CIContext = { [unowned self] in - return CIContext(MTLDevice: self.device) + return CIContext(mtlDevice: self.device) }() lazy var commandQueue: MTLCommandQueue = { [unowned self] in - return self.device.newCommandQueue() + return self.device.makeCommandQueue() }() lazy var defaultLibrary: MTLLibrary = @@ -46,11 +46,11 @@ class MetalFilter: CIFilter { [unowned self] in - let kernelFunction = self.defaultLibrary.newFunctionWithName(self.functionName)! + let kernelFunction = self.defaultLibrary.makeFunction(name: self.functionName)! do { - let pipelineState = try self.device.newComputePipelineStateWithFunction(kernelFunction) + let pipelineState = try self.device.makeComputePipelineState(function: kernelFunction) return pipelineState } catch @@ -85,7 +85,7 @@ class MetalFilter: CIFilter } if let imageFilter = self as? MetalImageFilter, - inputImage = imageFilter.inputImage + let inputImage = imageFilter.inputImage { return imageFromComputeShader(width: inputImage.extent.width, height: inputImage.extent.height, @@ -124,17 +124,17 @@ class MetalFilter: CIFilter fatalError("textureInvalid() not implemented in MetalFilter") } - func imageFromComputeShader(width width: CGFloat, height: CGFloat, inputImage: CIImage?) -> CIImage + func imageFromComputeShader(width: CGFloat, height: CGFloat, inputImage: CIImage?) -> CIImage { if textureDescriptor == nil { - textureDescriptor = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.RGBA8Unorm, + textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: Int(width), height: Int(height), mipmapped: false) - kernelInputTexture = device.newTextureWithDescriptor(textureDescriptor!) - kernelOutputTexture = device.newTextureWithDescriptor(textureDescriptor!) + kernelInputTexture = device.makeTexture(descriptor: textureDescriptor!) + kernelOutputTexture = device.makeTexture(descriptor: textureDescriptor!) threadgroupsPerGrid = MTLSizeMake( textureDescriptor!.width / threadsPerThreadgroup.width, @@ -143,55 +143,55 @@ class MetalFilter: CIFilter if clearOnStep { - kernelOutputTexture = device.newTextureWithDescriptor(textureDescriptor!) + kernelOutputTexture = device.makeTexture(descriptor: textureDescriptor!) } - let commandBuffer = commandQueue.commandBuffer() + let commandBuffer = commandQueue.makeCommandBuffer() if let imageFilter = self as? MetalImageFilter, - inputImage = imageFilter.inputImage + let inputImage = imageFilter.inputImage { ciContext.render(inputImage, - toMTLTexture: kernelInputTexture!, + to: kernelInputTexture!, commandBuffer: commandBuffer, bounds: inputImage.extent, colorSpace: colorSpace) } - let commandEncoder = commandBuffer.computeCommandEncoder() + let commandEncoder = commandBuffer.makeComputeCommandEncoder() commandEncoder.setComputePipelineState(pipelineState) // populate float buffers using kCIAttributeIdentity as buffer index - for inputKey in inputKeys where attributes[inputKey]?[kCIAttributeClass] == "NSNumber" + for inputKey in inputKeys where (attributes[inputKey] as? NSDictionary)?[kCIAttributeClass] as? String == "NSNumber" { if let bufferIndex = (attributes[inputKey] as! [String:AnyObject])[kCIAttributeIdentity] as? Int, - var bufferValue = valueForKey(inputKey) as? Float + var bufferValue = value(forKey: inputKey) as? Float { - let buffer = device.newBufferWithBytes(&bufferValue, - length: sizeof(Float), - options: MTLResourceOptions.CPUCacheModeDefaultCache) + let buffer = device.makeBuffer(bytes: &bufferValue, + length: MemoryLayout.size, + options: MTLResourceOptions()) - commandEncoder.setBuffer(buffer, offset: 0, atIndex: bufferIndex) + commandEncoder.setBuffer(buffer, offset: 0, at: bufferIndex) } } // populate color buffers using kCIAttributeIdentity as buffer index - for inputKey in inputKeys where attributes[inputKey]?[kCIAttributeClass] == "CIColor" + for inputKey in inputKeys where (attributes[inputKey] as? NSDictionary)?[kCIAttributeClass] as? String == "CIColor" { if let bufferIndex = (attributes[inputKey] as! [String:AnyObject])[kCIAttributeIdentity] as? Int, - bufferValue = valueForKey(inputKey) as? CIColor + let bufferValue = value(forKey: inputKey) as? CIColor { var color = float4(Float(bufferValue.red), Float(bufferValue.green), Float(bufferValue.blue), Float(bufferValue.alpha)) - let buffer = device.newBufferWithBytes(&color, - length: sizeof(float4), - options: MTLResourceOptions.CPUCacheModeDefaultCache) + let buffer = device.makeBuffer(bytes: &color, + length: MemoryLayout.size, + options: MTLResourceOptions()) - commandEncoder.setBuffer(buffer, offset: 0, atIndex: bufferIndex) + commandEncoder.setBuffer(buffer, offset: 0, at: bufferIndex) } } @@ -201,18 +201,18 @@ class MetalFilter: CIFilter { for indexedBuffer in indexedBuffers { - commandEncoder.setBuffer(indexedBuffer.buffer, offset: 0, atIndex: indexedBuffer.index) + commandEncoder.setBuffer(indexedBuffer.buffer, offset: 0, at: indexedBuffer.index) } } if self is MetalImageFilter { - commandEncoder.setTexture(kernelInputTexture, atIndex: 0) - commandEncoder.setTexture(kernelOutputTexture, atIndex: 1) + commandEncoder.setTexture(kernelInputTexture, at: 0) + commandEncoder.setTexture(kernelOutputTexture, at: 1) } else if self is MetalGeneratorFilter { - commandEncoder.setTexture(kernelOutputTexture, atIndex: 0) + commandEncoder.setTexture(kernelOutputTexture, at: 0) } commandEncoder.dispatchThreadgroups(customThreadgroupsPerGrid() ?? threadgroupsPerGrid!, @@ -222,8 +222,8 @@ class MetalFilter: CIFilter commandBuffer.commit() - return CIImage(MTLTexture: kernelOutputTexture!, - options: [kCIImageColorSpace: colorSpace]) + return CIImage(mtlTexture: kernelOutputTexture!, + options: [kCIImageColorSpace: colorSpace])! } } @@ -236,7 +236,7 @@ class MetalGeneratorFilter: MetalFilter override func textureInvalid() -> Bool { - if let textureDescriptor = textureDescriptor where + if let textureDescriptor = textureDescriptor, textureDescriptor.width != Int(inputWidth) || textureDescriptor.height != Int(inputHeight) { @@ -254,7 +254,7 @@ class MetalImageFilter: MetalFilter override func textureInvalid() -> Bool { if let textureDescriptor = textureDescriptor, - inputImage = inputImage where + let inputImage = inputImage, textureDescriptor.width != Int(inputImage.extent.width) || textureDescriptor.height != Int(inputImage.extent.height) { From b040559d7b0fba3db1308432a5ff415e799dd388 Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:14:40 +0200 Subject: [PATCH 3/7] Convert from Swift 3 to Swift 4.2, apply recommended project settings --- ParticleCam.xcodeproj/project.pbxproj | 29 ++++++++++++++++--- ParticleCam/AppDelegate.swift | 29 +------------------ ParticleCam/ParticleLab.swift | 2 +- ParticleCam/classes/CameraCaptureHelper.swift | 10 +++---- ParticleCam/classes/ImageView.swift | 10 +++---- ParticleCam/classes/MetalFilter.swift | 23 +++++++-------- 6 files changed, 48 insertions(+), 55 deletions(-) diff --git a/ParticleCam.xcodeproj/project.pbxproj b/ParticleCam.xcodeproj/project.pbxproj index c727fd4..f70d0de 100644 --- a/ParticleCam.xcodeproj/project.pbxproj +++ b/ParticleCam.xcodeproj/project.pbxproj @@ -121,13 +121,13 @@ 3EA930931B75BFFA0067276D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0700; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = "Simon Gladman"; TargetAttributes = { 3EA9309A1B75BFFA0067276D = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = 6CFA54MQCP; - LastSwiftMigration = 0830; + LastSwiftMigration = 1010; ProvisioningStyle = Manual; }; }; @@ -208,13 +208,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -253,13 +263,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -278,6 +298,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = 2; VALIDATE_PRODUCT = YES; }; @@ -297,7 +318,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = "(dan jan 16) Dev Any App"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -316,7 +337,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/ParticleCam/AppDelegate.swift b/ParticleCam/AppDelegate.swift index bebad36..081511c 100644 --- a/ParticleCam/AppDelegate.swift +++ b/ParticleCam/AppDelegate.swift @@ -13,34 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } - - func applicationWillResignActive(_ application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - } - diff --git a/ParticleCam/ParticleLab.swift b/ParticleCam/ParticleLab.swift index 9b5b1f1..1b9e425 100644 --- a/ParticleCam/ParticleLab.swift +++ b/ParticleCam/ParticleLab.swift @@ -41,7 +41,7 @@ class ParticleCamFilter: MetalImageFilter return self.device.makeBuffer(bytesNoCopy: self.particlesMemory!, length: Int(self.particlesMemoryByteSize), options: MTLResourceOptions(), - deallocator: nil) + deallocator: nil)! }() let particleSize = MemoryLayout.size diff --git a/ParticleCam/classes/CameraCaptureHelper.swift b/ParticleCam/classes/CameraCaptureHelper.swift index a20e0b9..81a4bf1 100644 --- a/ParticleCam/classes/CameraCaptureHelper.swift +++ b/ParticleCam/classes/CameraCaptureHelper.swift @@ -20,11 +20,11 @@ import UIKit class CameraCaptureHelper: NSObject { let captureSession = AVCaptureSession() - let cameraPosition: AVCaptureDevicePosition + let cameraPosition: AVCaptureDevice.Position weak var delegate: CameraCaptureHelperDelegate? - required init(cameraPosition: AVCaptureDevicePosition) + required init(cameraPosition: AVCaptureDevice.Position) { self.cameraPosition = cameraPosition @@ -35,9 +35,9 @@ class CameraCaptureHelper: NSObject fileprivate func initialiseCaptureSession() { - captureSession.sessionPreset = AVCaptureSessionPresetiFrame1280x720 + captureSession.sessionPreset = .iFrame1280x720 - guard let camera = (AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as! [AVCaptureDevice]) + guard let camera = AVCaptureDevice.devices(for: .video) .filter({ $0.position == cameraPosition }) .first else { @@ -71,7 +71,7 @@ class CameraCaptureHelper: NSObject extension CameraCaptureHelper: AVCaptureVideoDataOutputSampleBufferDelegate { - func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) + func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! diff --git a/ParticleCam/classes/ImageView.swift b/ParticleCam/classes/ImageView.swift index 8aa4a2f..b5f9c9a 100644 --- a/ParticleCam/classes/ImageView.swift +++ b/ParticleCam/classes/ImageView.swift @@ -22,7 +22,7 @@ class MetalImageView: MTKView { [unowned self] in - return self.device!.makeCommandQueue() + return self.device!.makeCommandQueue()! }() lazy var ciContext: CIContext = @@ -68,7 +68,7 @@ class MetalImageView: MTKView return } - let commandBuffer = commandQueue.makeCommandBuffer() + let commandBuffer = commandQueue.makeCommandBuffer()! let bounds = CGRect(origin: CGPoint.zero, size: drawableSize) @@ -80,8 +80,8 @@ class MetalImageView: MTKView let scale = min(scaleX, scaleY) let scaledImage = image - .applying(CGAffineTransform(translationX: -originX, y: -originY)) - .applying(CGAffineTransform(scaleX: scale, y: scale)) + .transformed(by: CGAffineTransform(translationX: -originX, y: -originY)) + .transformed(by: CGAffineTransform(scaleX: scale, y: scale)) ciContext.render(scaledImage, to: targetTexture, @@ -112,7 +112,7 @@ class OpenGLImageView: GLKView [unowned self] in return CIContext(eaglContext: self.eaglContext!, - options: [kCIContextWorkingColorSpace: NSNull()]) + options: [.workingColorSpace: NSNull()]) }() override init(frame: CGRect) diff --git a/ParticleCam/classes/MetalFilter.swift b/ParticleCam/classes/MetalFilter.swift index 0c26545..41c3d10 100644 --- a/ParticleCam/classes/MetalFilter.swift +++ b/ParticleCam/classes/MetalFilter.swift @@ -32,14 +32,14 @@ class MetalFilter: CIFilter { [unowned self] in - return self.device.makeCommandQueue() + return self.device.makeCommandQueue()! }() lazy var defaultLibrary: MTLLibrary = { [unowned self] in - return self.device.newDefaultLibrary()! + return self.device.makeDefaultLibrary()! }() lazy var pipelineState: MTLComputePipelineState = @@ -146,7 +146,7 @@ class MetalFilter: CIFilter kernelOutputTexture = device.makeTexture(descriptor: textureDescriptor!) } - let commandBuffer = commandQueue.makeCommandBuffer() + let commandBuffer = commandQueue.makeCommandBuffer()! if let imageFilter = self as? MetalImageFilter, let inputImage = imageFilter.inputImage @@ -158,7 +158,7 @@ class MetalFilter: CIFilter colorSpace: colorSpace) } - let commandEncoder = commandBuffer.makeComputeCommandEncoder() + let commandEncoder = commandBuffer.makeComputeCommandEncoder()! commandEncoder.setComputePipelineState(pipelineState) @@ -172,7 +172,7 @@ class MetalFilter: CIFilter length: MemoryLayout.size, options: MTLResourceOptions()) - commandEncoder.setBuffer(buffer, offset: 0, at: bufferIndex) + commandEncoder.setBuffer(buffer, offset: 0, index: bufferIndex) } } @@ -191,7 +191,7 @@ class MetalFilter: CIFilter length: MemoryLayout.size, options: MTLResourceOptions()) - commandEncoder.setBuffer(buffer, offset: 0, at: bufferIndex) + commandEncoder.setBuffer(buffer, offset: 0, index: bufferIndex) } } @@ -201,18 +201,18 @@ class MetalFilter: CIFilter { for indexedBuffer in indexedBuffers { - commandEncoder.setBuffer(indexedBuffer.buffer, offset: 0, at: indexedBuffer.index) + commandEncoder.setBuffer(indexedBuffer.buffer, offset: 0, index: indexedBuffer.index) } } if self is MetalImageFilter { - commandEncoder.setTexture(kernelInputTexture, at: 0) - commandEncoder.setTexture(kernelOutputTexture, at: 1) + commandEncoder.setTexture(kernelInputTexture, index: 0) + commandEncoder.setTexture(kernelOutputTexture, index: 1) } else if self is MetalGeneratorFilter { - commandEncoder.setTexture(kernelOutputTexture, at: 0) + commandEncoder.setTexture(kernelOutputTexture, index: 0) } commandEncoder.dispatchThreadgroups(customThreadgroupsPerGrid() ?? threadgroupsPerGrid!, @@ -223,7 +223,7 @@ class MetalFilter: CIFilter commandBuffer.commit() return CIImage(mtlTexture: kernelOutputTexture!, - options: [kCIImageColorSpace: colorSpace])! + options: [.colorSpace: colorSpace])! } } @@ -264,4 +264,3 @@ class MetalImageFilter: MetalFilter return false } } - From 2407ef3b9cc38fd202e10b5afd688a79e7c4697a Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:19:02 +0200 Subject: [PATCH 4/7] Adds camera usage description --- ParticleCam/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ParticleCam/Info.plist b/ParticleCam/Info.plist index 879c6cf..39e0209 100644 --- a/ParticleCam/Info.plist +++ b/ParticleCam/Info.plist @@ -42,5 +42,7 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSCameraUsageDescription + Camera access is used to get live video feed for particle processing From 3cadfeff9446a1e3312f73a87b9bcbe48c8e22b6 Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:19:24 +0200 Subject: [PATCH 5/7] (MetalFilter) adds texture usage (fix runtime crash) --- ParticleCam/classes/MetalFilter.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ParticleCam/classes/MetalFilter.swift b/ParticleCam/classes/MetalFilter.swift index 41c3d10..1f00ce1 100644 --- a/ParticleCam/classes/MetalFilter.swift +++ b/ParticleCam/classes/MetalFilter.swift @@ -133,6 +133,7 @@ class MetalFilter: CIFilter height: Int(height), mipmapped: false) + textureDescriptor?.usage = [.shaderRead, .shaderWrite] kernelInputTexture = device.makeTexture(descriptor: textureDescriptor!) kernelOutputTexture = device.makeTexture(descriptor: textureDescriptor!) From 6951391d36c36d4fa5bf4bb227b13006575c4ad0 Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:30:27 +0200 Subject: [PATCH 6/7] (CameraCaptureHelper) use videoOrientation property instead of using UIKit api on a background thread --- ParticleCam/classes/CameraCaptureHelper.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ParticleCam/classes/CameraCaptureHelper.swift b/ParticleCam/classes/CameraCaptureHelper.swift index 81a4bf1..1852c74 100644 --- a/ParticleCam/classes/CameraCaptureHelper.swift +++ b/ParticleCam/classes/CameraCaptureHelper.swift @@ -23,6 +23,13 @@ class CameraCaptureHelper: NSObject let cameraPosition: AVCaptureDevice.Position weak var delegate: CameraCaptureHelperDelegate? + var videoOrientation: AVCaptureVideoOrientation = .landscapeLeft + + var statusBarObserveHandle: NSObjectProtocol? + + deinit { + statusBarObserveHandle.map { NotificationCenter.default.removeObserver($0) } + } required init(cameraPosition: AVCaptureDevice.Position) { @@ -31,6 +38,11 @@ class CameraCaptureHelper: NSObject super.init() initialiseCaptureSession() + + videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! + statusBarObserveHandle = NotificationCenter.default.addObserver(forName: UIApplication.didChangeStatusBarOrientationNotification, object: nil, queue: nil) { [weak self] _ in + self?.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! + } } fileprivate func initialiseCaptureSession() @@ -73,7 +85,7 @@ extension CameraCaptureHelper: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! + connection.videoOrientation = videoOrientation guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { From d76d43497fcc2138d9db3560eb5afa7138dea71e Mon Sep 17 00:00:00 2001 From: Dan Pashchenko Date: Fri, 15 Feb 2019 17:42:26 +0200 Subject: [PATCH 7/7] (MetalImageView) render in draw() instead of image-didSet to fix console notification on repeated currentDrawable usage --- ParticleCam/classes/ImageView.swift | 90 ++++++++++++++--------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/ParticleCam/classes/ImageView.swift b/ParticleCam/classes/ImageView.swift index b5f9c9a..7c1238d 100644 --- a/ParticleCam/classes/ImageView.swift +++ b/ParticleCam/classes/ImageView.swift @@ -19,23 +19,23 @@ class MetalImageView: MTKView let colorSpace = CGColorSpaceCreateDeviceRGB() lazy var commandQueue: MTLCommandQueue = - { - [unowned self] in - - return self.device!.makeCommandQueue()! - }() + { + [unowned self] in + + return self.device!.makeCommandQueue()! + }() lazy var ciContext: CIContext = - { - [unowned self] in - - return CIContext(mtlDevice: self.device!) - }() + { + [unowned self] in + + return CIContext(mtlDevice: self.device!) + }() override init(frame frameRect: CGRect, device: MTLDevice?) { super.init(frame: frameRect, - device: device ?? MTLCreateSystemDefaultDevice()) + device: device ?? MTLCreateSystemDefaultDevice()) if super.device == nil { @@ -52,17 +52,11 @@ class MetalImageView: MTKView /// The image to display var image: CIImage? - { - didSet - { - renderImage() - } - } - - func renderImage() - { - guard let - image = image, + + override func draw() { + super.draw() + + guard let image = image, let targetTexture = currentDrawable?.texture else { return @@ -84,10 +78,10 @@ class MetalImageView: MTKView .transformed(by: CGAffineTransform(scaleX: scale, y: scale)) ciContext.render(scaledImage, - to: targetTexture, - commandBuffer: commandBuffer, - bounds: bounds, - colorSpace: colorSpace) + to: targetTexture, + commandBuffer: commandBuffer, + bounds: bounds, + colorSpace: colorSpace) commandBuffer.present(currentDrawable!) @@ -108,12 +102,12 @@ class OpenGLImageView: GLKView let eaglContext = EAGLContext(api: .openGLES2) lazy var ciContext: CIContext = - { - [unowned self] in - - return CIContext(eaglContext: self.eaglContext!, - options: [.workingColorSpace: NSNull()]) - }() + { + [unowned self] in + + return CIContext(eaglContext: self.eaglContext!, + options: [.workingColorSpace: NSNull()]) + }() override init(frame: CGRect) { @@ -135,7 +129,7 @@ class OpenGLImageView: GLKView /// The image to display var image: CIImage? - { + { didSet { setNeedsDisplay() @@ -154,25 +148,25 @@ extension OpenGLImageView: GLKViewDelegate let targetRect = image.extent.aspectFitInRect( target: CGRect(origin: CGPoint.zero, - size: CGSize(width: drawableWidth, - height: drawableHeight))) + size: CGSize(width: drawableWidth, + height: drawableHeight))) let ciBackgroundColor = CIColor( color: backgroundColor ?? UIColor.white) ciContext.draw(CIImage(color: ciBackgroundColor), - in: CGRect(x: 0, - y: 0, - width: drawableWidth, - height: drawableHeight), - from: CGRect(x: 0, - y: 0, - width: drawableWidth, - height: drawableHeight)) + in: CGRect(x: 0, + y: 0, + width: drawableWidth, + height: drawableHeight), + from: CGRect(x: 0, + y: 0, + width: drawableWidth, + height: drawableHeight)) ciContext.draw(image, - in: targetRect, - from: image.extent) + in: targetRect, + from: image.extent) } } @@ -195,8 +189,8 @@ extension CGRect let y = target.midY - height / 2 return CGRect(x: x, - y: y, - width: width, - height: height) + y: y, + width: width, + height: height) } }