How to blur a scene in Swift 2 - ios

I'm trying to blur a scene when I pause a game and I'm following an example but I'm unable to work it out in Swift 2.0.
A lot of tutorials say to just take a screenshot and then present that screenshot as blurred but I don't think that's a good idea, I'd like to blur the view without a screenshot.
here is my attempt:
func createlayers() {
let node = SKEffectNode()
node.shouldEnableEffects = false
let filter: CIFilter = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius" : NSNumber(double:1.0)])!
node.filter = filter
}
func blurWithCompletion() {
let duration: CGFloat = 0.5
scene!.shouldRasterize = true
scene!.shouldEnableEffects = true
scene!.runAction(SKAction.customActionWithDuration(0.5, actionBlock: { (node: SKNode, elapsedTime: CGFloat) in
let radius = (elapsedTime/duration)*10.0
(node as? SKEffectNode)!.filter!.setValue(radius, forKey: "inputRadius")
}))
}
func pauseGame()
{
self.blurWithCompletion()
self.view!.paused = true
}
I get "fatal error: unexpectedly found nil while unwrapping an Optional value"

create layers method is not required.
Use this updated blurWithCompletion method:
func blurWithCompletion() {
let duration: CGFloat = 0.5
let filter: CIFilter = CIFilter(name: "CIGaussianBlur", withInputParameters: ["inputRadius" : NSNumber(double:1.0)])!
scene!.filter = filter
scene!.shouldRasterize = true
scene!.shouldEnableEffects = true
scene!.runAction(SKAction.customActionWithDuration(0.5, actionBlock: { (node: SKNode, elapsedTime: CGFloat) in
let radius = (elapsedTime/duration)*10.0
(node as? SKEffectNode)!.filter!.setValue(radius, forKey: "inputRadius")
}))
}

Related

How do you apply Core Image filters to an onscreen image using Swift/MacOS or iOS and Core Image

Photos editing adjustments provides a realtime view of the applied adjustments as they are applied. I wasn't able to find any samples of how you do this. All the examples seems to show that you apply the filters through a pipeline of sorts and then take the resulting image and update the screen with the result. See code below.
Photos seems to show the adjustment applied to the onscreen image. How do they achieve this?
func editImage(inputImage: CGImage) {
DispatchQueue.global().async {
let beginImage = CIImage(cgImage: inputImage)
guard let exposureOutput = self.exposureFilter(beginImage, ev: self.brightness) else {
return
}
guard let vibranceOutput = self.vibranceFilter(exposureOutput, amount: self.vibranceAmount) else {
return
}
guard let unsharpMaskOutput = self.unsharpMaskFilter(vibranceOutput, intensity: self.unsharpMaskIntensity, radius: self.unsharpMaskRadius) else {
return
}
guard let sharpnessOutput = self.sharpenFilter(unsharpMaskOutput, sharpness: self.unsharpMaskIntensity) else {
return
}
if let cgimg = self.context.createCGImage(sharpnessOutput, from: vibranceOutput.extent) {
DispatchQueue.main.async {
self.cgImage = cgimg
}
}
}
}
OK, I just found the answer - use MTKView, which is working fine except for getting the image to fill the view correctly!
For the benefit of others here are the basics... I have yet to figure out how to position the image correctly in the view - but I can see the filter applied in realtime!
class ViewController: NSViewController, MTKViewDelegate {
....
#objc dynamic var cgImage: CGImage? {
didSet {
if let cgimg = cgImage {
ciImage = CIImage(cgImage: cgimg)
}
}
}
var ciImage: CIImage?
// Metal resources
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
var sourceTexture: MTLTexture! // 2
let colorSpace = CGColorSpaceCreateDeviceRGB()
var context: CIContext!
var textureLoader: MTKTextureLoader!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let metalView = MTKView()
metalView.translatesAutoresizingMaskIntoConstraints = false
self.imageView.addSubview(metalView)
NSLayoutConstraint.activate([
metalView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
metalView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
metalView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
metalView.topAnchor.constraint(equalTo: view.topAnchor)
])
device = MTLCreateSystemDefaultDevice()
commandQueue = device.makeCommandQueue()
metalView.delegate = self
metalView.device = device
metalView.framebufferOnly = false
context = CIContext()
textureLoader = MTKTextureLoader(device: device)
}
public func draw(in view: MTKView) {
if let ciImage = self.ciImage {
if let currentDrawable = view.currentDrawable {
let commandBuffer = commandQueue.makeCommandBuffer()
let inputImage = ciImage // 2
exposureFilter.setValue(inputImage, forKey: kCIInputImageKey)
exposureFilter.setValue(ev, forKey: kCIInputEVKey)
context.render(exposureFilter.outputImage!,
to: currentDrawable.texture,
commandBuffer: commandBuffer,
bounds: CGRect(origin: .zero, size: view.drawableSize),
colorSpace: colorSpace)
commandBuffer?.present(currentDrawable)
commandBuffer?.commit()
}
}
}

Xcode- Image View Not Changing

I am trying to update a UIImageView using a for loop, however, the image only shows up on the last iteration of the loop. The code is intended to display a QR code, wait 4 seconds, then display a different QR code, however it only shows the last QR code.
for record in 1...importedrecords.count-3 {
let qrCode = importedrecords[record][0] + " " + importedrecords[record][1] + " " + importedrecords[record][7]
print("QR Code is: " + qrCode)
let data = qrCode.data(using: .ascii, allowLossyConversion: false)
let filter = CIFilter(name: "CIQRCodeGenerator")
filter?.setValue(data, forKey: "InputMessage")
let ciImage = filter?.outputImage
let transform = CGAffineTransform(scaleX: 10, y: 10)
let transformImage = ciImage?.transformed(by: transform)
let image = UIImage(ciImage: transformImage!)
self.myImageView.image = image
do {
sleep(4)
}
}
I suggest to use Timer for this. Using sleep is not recommended, especially on the main thread, since it stops all the work being done on the thread.
var timer: Timer?
var recordIndex = 0
var importedRecords = [[String]]()
// Call this when you want to start updating
func startUpdate() {
timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector:#selector(updateQR), userInfo: nil, repeats: true)
}
// Call this when you want to stop and reset updating
func stopUpdate() {
timer?.invalidate()
recordIndex = 0
}
#objc
func updateQR() {
let qrCode = importedRecords[recordIndex][0] + " " + importedRecords[recordIndex][1] + " " + importedRecords[recordIndex][7]
print("QR Code is: " + qrCode)
let data = qrCode.data(using: .ascii, allowLossyConversion: false)
let filter = CIFilter(name: "CIQRCodeGenerator")
filter?.setValue(data, forKey: "InputMessage")
let ciImage = filter?.outputImage
let transform = CGAffineTransform(scaleX: 10, y: 10)
let transformImage = ciImage?.transformed(by: transform)
let image = UIImage(ciImage: transformImage!)
myImageView.image = image
// This will prevent out of bounds crash
if recordIndex < importedRecords.count {
recordIndex += 1
} else {
stopUpdate()
}
}

Adding multiple effects to a photo

How can I add several effects to a picture? I have the following code that adds an effect to a photo:
func applyEffects(name: String, n: Float) {
filter.setValue(self.cImage, forKeyPath: kCIInputImageKey)
filter.setValue(n, forKeyPath: name)
let result = filter.value(forKey: kCIOutputImageKey) as! CIImage
let cgImage = CIContext.init(options: nil).createCGImage(result, from: result.extent)
self.customImage = UIImage.init(cgImage: cgImage!)
}
func brightness(n: Float) {
self.applyEffects(name: kCIInputBrightnessKey, n: n)
}
func contrast(n: Float) {
self.applyEffects(name: kCIInputContrastKey, n: n)
}
func saturation(n: Float) {
self.applyEffects(name: kCIInputSaturationKey, n: n)
}
But when I want to apply the second effect, the first one disappears. How can I overlay two or more effects on each other?
I'm assuming you are using CIColorControls as your filter.
You need to pass all three values into your call:
// The documentation doesn't give a default value for contrast, but for the others, I'm setting the defaults
var brightness:Float = 1
var contrast:Float = 1
var saturation:Float = 1
func applyEffects() {
filter.setValue(self.cImage, forKeyPath: kCIInputImageKey)
filter.setValue(brightness, forKeyPath: kCIInputBrightnessKey)
filter.setValue(contrast, forKeyPath: kCIInputContrastKey)
filter.setValue(saturation, forKeyPath: kCIInputSaturationKey)
let result = filter.value(forKey: kCIOutputImageKey) as! CIImage
let cgImage = CIContext.init(options: nil).createCGImage(result, from: result.extent)
self.customImage = UIImage.init(cgImage: cgImage!)
}
func brightness(n: Float) {
brightness = n
applyEffects()
}
func contrast(n: Float) {
contrast = n
applyEffects()
}
func saturation(n: Float) {
saturation = n
applyEffects()
}
A suggestion:
If you are trying to use "real-time" updating via UISliders, use a GLKView and send in the CIImage directly. It uses the GPU, and performance on a device is greatly increased. You can always create a UIImage for saving, messaging, etc.

Animate spinning image in Swift

I have a image that I would like to keep spinning if a button is pressed, until I call an action to stop the spinning. I tried these websites: https://www.andrewcbancroft.com/2014/10/15/rotate-animation-in-swift/ I had to made chances to the delegation, it crashes when I try to changed it
and https://bencoding.com/2015/07/27/spinning-uiimageview-using-swift/ just does not spin when I call the action. Thank you. Code is here below:
Andrewcbancroft.com:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate //<- error "Cannot assign value of type 'AnyObject' to type 'CAAnimationDelegate?'"
}
self.layer.add(rotateAnimation, forKey: nil)
}
}
Trying to cast delegate as! CAAnimationDelegate will crash the application with error code Could not cast value of type 'test.ViewController' (0x1074a0a60) to 'CAAnimationDelegate' (0x10cede480) when trying to rotate the image.
Bending.com:
extension UIView {
func startRotating(duration: Double = 1) {
let kAnimationKey = "rotation"
if self.layer.animation(forKey: kAnimationKey) == nil {
let animate = CABasicAnimation(keyPath: "transform.rotation")
animate.duration = duration
animate.repeatCount = Float.infinity
animate.fromValue = 0.0
animate.toValue = Float(M_PI * 2.0)
self.layer.add(animate, forKey: kAnimationKey)
}
}
func stopRotating() {
let kAnimationKey = "rotation"
if self.layer.animation(forKey: kAnimationKey) != nil {
self.layer.removeAnimation(forKey: kAnimationKey)
}
}
}
When trying to call my image view to start rotating in my viewDidLoad method, nothing happens.
Errors explained:
You're passing the delegate in as AnyObject
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil)
AnyObject doesn't conform to CAAnimationDelegate, so you can't use it as the delegate.
If you're certain you're passing in a controller that can be a proper delegate, then cast it to the right kind of delegate:
if let delegate = completionDelegate as? CAAnimationDelegate {
rotateAnimation.delegate = delegate
}
If you're doing this and it's crashing:
rotateAnimation.delegate = completionDelegate as! CAAnimationDelegate
Then you're not passing in a controller that conforms to CAAnimationDelegate
It might be simpler to not pass the delegate in at all and assign it outside of the extension.

Why is my custom MKTileOverlayRenderer drawing the same tile in multiple places?

So I'm writing a MapKit-based app which draws an overlay over the map. However, a lot of the overlay drawing is dynamic, such that tile which gets drawn is frequently changing, so I've implemented a custom MKTileOverlay and a custom MKTileOverlayRenderer. The first one to handle the url-scheme for where the tile images are stored, and the second to handle the custom drawMapRect implementation.
The issue I'm running into is that I seem to be drawing the same tile image in multiple locations. Here's a screenshot to help you visualize what I mean: (I know the tiles are upside-down and backwards and I can fix that)
iOS Simulator Screenshot
I've changed certain tile images such that they're a different color and have their tile path included. What you'll notice is that many of the tile images are repeated over different areas.
I've been trying to figure out why that might be happening, so following my code path, the overlay starting point is pretty standard--the ViewController sets the addOverlay() call, which calls the delegates' mapView(rendererForOverlay:) which returns my custom MKTileOverlayRenderer class, which then attempts to call my drawMapRect(mapRect:, zoomScale:, context). It then takes the given map_rect and calculates which tile that map_rect belongs to, calls the custom MKTileOverlay class's loadTileAtPath() and then draws the resulting tile image data. And that's exactly what it looks like my code is doing as well, so I'm not really sure where I'm going wrong. That said, it works perfectly fine if I'm not trying to implement custom drawing and use a default MKTileOverlayRenderer. Unfortunately, that's also the crux of the app so not really a viable solution.
For reference, here's the relevant code from my custom classes:
My custom MKTileOverlay class
class ExploredTileOverlay: MKTileOverlay {
var base_path: String
//var tile_path: String?
let cache: NSCache = NSCache()
var point_buffer: ExploredSegment
var last_tile_path: MKTileOverlayPath?
var tile_buffer: ExploredTiles
init(URLTemplate: String?, startingLocation location: CLLocation, city: City) {
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentsDirectory: AnyObject = paths[0]
self.base_path = documentsDirectory.stringByAppendingPathComponent("/" + city.name + "_tiles")
if (!NSFileManager.defaultManager().fileExistsAtPath(base_path)) {
try! NSFileManager.defaultManager().createDirectoryAtPath(base_path, withIntermediateDirectories: false, attributes: nil)
}
let new_point = MKMapPointForCoordinate(location.coordinate)
self.point_buffer = ExploredSegment(fromPoint: new_point, inCity: city)
self.tile_buffer = ExploredTiles(startingPoint: ExploredPoint(mapPoint: new_point, r: 50))
self.last_tile_path = Array(tile_buffer.edited_tiles.values).last!.path
super.init(URLTemplate: URLTemplate)
}
override func URLForTilePath(path: MKTileOverlayPath) -> NSURL {
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
//print("fetching tile " + filled_template)
if !NSFileManager.defaultManager().fileExistsAtPath(tile_path) {
return NSURL(fileURLWithPath: "")
}
return NSURL(fileURLWithPath: tile_path)
}
override func loadTileAtPath(path: MKTileOverlayPath, result: (NSData?, NSError?) -> Void) {
let url = URLForTilePath(path)
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
if (url != NSURL(fileURLWithPath: tile_path)) {
print("creating tile at " + String(path))
let img_data: NSData = UIImagePNGRepresentation(UIImage(named: "small")!)!
let filled_template = String(format: "%d_%d_%d.png", path.z, path.x, path.y)
let tile_path = base_path + "/" + filled_template
img_data.writeToFile(tile_path, atomically: true)
cache.setObject(img_data, forKey: url)
result(img_data, nil)
return
} else if let cachedData = cache.objectForKey(url) as? NSData {
print("using cache for " + String(path))
result(cachedData, nil)
return
} else {
print("loading " + String(path) + " from directory")
let img_data: NSData = UIImagePNGRepresentation(UIImage(contentsOfFile: tile_path)!)!
cache.setObject(img_data, forKey: url)
result(img_data, nil)
return
}
}
My custom MKTileOverlayRenderer class:
class ExploredTileRenderer: MKTileOverlayRenderer {
let tile_overlay: ExploredTileOverlay
var zoom_scale: MKZoomScale?
let cache: NSCache = NSCache()
override init(overlay: MKOverlay) {
self.tile_overlay = overlay as! ExploredTileOverlay
super.init(overlay: overlay)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(saveEditedTiles), name: "com.Coder.Wander.reachedMaxPoints", object: nil)
}
// There's some weird cache-ing thing that requires me to recall it
// whenever I re-draw over the tile, I don't really get it but it works
override func canDrawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale) -> Bool {
self.setNeedsDisplayInMapRect(mapRect, zoomScale: zoomScale)
return true
}
override func drawMapRect(mapRect: MKMapRect, zoomScale: MKZoomScale, inContext context: CGContext) {
zoom_scale = zoomScale
let tile_path = self.tilePathForMapRect(mapRect, andZoomScale: zoomScale)
let tile_path_string = stringForTilePath(tile_path)
//print("redrawing tile: " + tile_path_string)
self.tile_overlay.loadTileAtPath(tile_path, result: {
data, error in
if error == nil && data != nil {
if let image = UIImage(data: data!) {
let draw_rect = self.rectForMapRect(mapRect)
CGContextDrawImage(context, draw_rect, image.CGImage)
var path: [(CGMutablePath, CGFloat)]? = nil
self.tile_overlay.point_buffer.readPointsWithBlockAndWait({ points in
let total = self.getPathForPoints(points, zoomScale: zoomScale, offset: MKMapPointMake(0.0, 0.0))
path = total.0
//print("number of points: " + String(path!.count))
})
if ((path != nil) && (path!.count > 0)) {
//print("drawing path")
for segment in path! {
CGContextAddPath(context, segment.0)
CGContextSetBlendMode(context, .Clear)
CGContextSetLineJoin(context, CGLineJoin.Round)
CGContextSetLineCap(context, CGLineCap.Round)
CGContextSetLineWidth(context, segment.1)
CGContextStrokePath(context)
}
}
}
}
})
}
And my helper functions that handle converting between zoomScale, zoomLevel, tile path, and tile coordinates:
func tilePathForMapRect(mapRect: MKMapRect, andZoomScale zoom: MKZoomScale) -> MKTileOverlayPath {
let zoom_level = self.zoomLevelForZoomScale(zoom)
let mercatorPoint = self.mercatorTileOriginForMapRect(mapRect)
//print("mercPt: " + String(mercatorPoint))
let tilex = Int(floor(Double(mercatorPoint.x) * self.worldTileWidthForZoomLevel(zoom_level)))
let tiley = Int(floor(Double(mercatorPoint.y) * self.worldTileWidthForZoomLevel(zoom_level)))
return MKTileOverlayPath(x: tilex, y: tiley, z: zoom_level, contentScaleFactor: UIScreen.mainScreen().scale)
}
func stringForTilePath(path: MKTileOverlayPath) -> String {
return String(format: "%d_%d_%d", path.z, path.x, path.y)
}
func zoomLevelForZoomScale(zoomScale: MKZoomScale) -> Int {
let real_scale = zoomScale / UIScreen.mainScreen().scale
var z = Int((log2(Double(real_scale))+20.0))
z += (Int(UIScreen.mainScreen().scale) - 1)
return z
}
func worldTileWidthForZoomLevel(zoomLevel: Int) -> Double {
return pow(2, Double(zoomLevel))
}
func mercatorTileOriginForMapRect(mapRect: MKMapRect) -> CGPoint {
let map_region: MKCoordinateRegion = MKCoordinateRegionForMapRect(mapRect)
var x : Double = map_region.center.longitude * (M_PI/180.0)
var y : Double = map_region.center.latitude * (M_PI/180.0)
y = log10(tan(y) + 1.0/cos(y))
x = (1.0 + (x/M_PI)) / 2.0
y = (1.0 - (y/M_PI)) / 2.0
return CGPointMake(CGFloat(x), CGFloat(y))
}
This is a pretty obscure error, I think, so haven't had a whole lot of luck finding other people facing similar issues. Anything would help!

Resources