Search code examples
swiftsprite-kitcore-imagensexceptionskeffectnode

Core Image NSException Crash, why/what?


In SpriteKit, using an Effect node, I apply a simple gradient to a simple rectangle.

That seems to work fine, however adding it to the scene causes a NSException.

PLEASE: What am I doing wrong?


import SpriteKit
import GameplayKit
import CoreImage

class GameScene: SKScene {

    override func didMove(to view: SKView) {

        let rectangle = SKSpriteNode(color: .black, size: CGSize(width: 480, height: 80))

        let gradientOne = CIFilter(name: "CILinearGradient", withInputParameters:[
            "inputPoint0" : CIVector(x: 0,  y: 0),  "inputColor0" : SKColor.red,
            "inputPoint1" : CIVector(x: 200,y: 0),  "inputColor1" : SKColor.blue])
//
        let effectGradient = SKEffectNode()
            effectGradient.filter = gradientOne
            effectGradient.addChild(rectangle)
            effectGradient.blendMode = .alpha
            effectGradient.shouldRasterize = true

        addChild(effectGradient) //***** CRASH IS HERE *****
    }
}

Here's the output of the crunch:

    0   CoreFoundation                      0x000000010886ad4b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010565021e objc_exception_throw + 48
    2   CoreFoundation                      0x000000010886ac99 -[NSException raise] + 9
    3   CoreImage                           0x0000000105b2f34d -[CIFilter setValue:forUndefinedKey:] + 137
    4   Foundation                          0x000000010515e9df -[NSObject(NSKeyValueCoding) setValue:forKey:] + 291
    5   SpriteKit                           0x0000000105efa395 _ZN13SKCEffectNode17addRequistePassesEP13SKCRenderInfoPNSt3__14listINS2_10shared_ptrI13SKCRenderPassEENS2_9allocatorIS6_EEEE + 377
    6   SpriteKit                           0x0000000105f3fbde _ZN11SKCRenderer18doBuildRenderGroupEP7SKCNode14SKCRenderState15matrix_float4x4 + 488
    7   SpriteKit                           0x0000000105f3fecb _ZN11SKCRenderer18doBuildRenderGroupEP7SKCNode14SKCRenderState15matrix_float4x4 + 1237
    8   SpriteKit                           0x0000000105f3ef1c _ZN11SKCRenderer16buildRenderGroupERKNSt3__110shared_ptrI18SKCRenderSortGroupEE + 1264
    9   SpriteKit                           0x0000000105f3b686 _ZN11SKCRenderer15buildRenderPassERKNSt3__110shared_ptrI13SKCRenderPassEE + 88
    10  SpriteKit                           0x0000000105f3af7e _ZN11SKCRenderer6renderEP7SKCNodeDv4_fRKNSt3__110shared_ptrI15jet_framebufferEEDv4_j15matrix_float4x4bP12NSDictionaryP8SKCStatsSE_d + 2150
    11  SpriteKit                           0x0000000105ebec09 __59-[SKView _renderSynchronouslyForTime:preRender:postRender:]_block_invoke + 855
    12  SpriteKit                           0x0000000105ebe82a -[SKView _renderSynchronouslyForTime:preRender:postRender:] + 249
    13  SpriteKit                           0x0000000105ec1884 -[SKView layoutSubviews] + 177
    14  UIKit                               0x0000000106187ab8 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1237
    15  QuartzCore                          0x000000010db60bf8 -[CALayer layoutSublayers] + 146
    16  QuartzCore                          0x000000010db54440 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
    17  QuartzCore                          0x000000010db542be _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
    18  QuartzCore                          0x000000010dae2318 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 280
    19  QuartzCore                          0x000000010db0f3ff _ZN2CA11Transaction6commitEv + 475
    20  QuartzCore                          0x000000010db0fd6f _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 113
    21  CoreFoundation                      0x000000010880f267 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    22  CoreFoundation                      0x000000010880f1d7 __CFRunLoopDoObservers + 391
    23  CoreFoundation                      0x00000001087f38a6 CFRunLoopRunSpecific + 454
    24  UIKit                               0x00000001060bcaea -[UIApplication _run] + 434
    25  UIKit                               0x00000001060c2c68 UIApplicationMain + 159
    26  testinGradients                     0x000000010506e73f main + 111
    27  libdyld.dylib                       0x000000010981a68d start + 1
    28  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

UPDATE: FURTHER CONFUSION!!!

Despite Benzi's answer working, I'm confused.

Of his technique, three things confuse:

Confusion 1:

  var inputImage: CIImage?

To me, this looks like inputImage is not initialised, not created, only defined as being of type CIImage — OPTIONAL! So it doesn’t yet exist.

Confusion 2:

override var outputImage: CIImage? {

How does this closure work, why is it here?

Confusion 3:

guard let inputImage = inputImage else {
            return nil
        }

This says make inputImage a CIImage, but where does its contents and definition come from? Size, shape, etc… where from?

We know its getting details of the node instance of SKSpriteNode passed into here, somehow, because it’s used - right here:

let extent = inputImage.extent

But HOW does the SKSpriteNode added as a child of SKEffectNode become the inputImage value within the CIFilter subclass?


Solution

  • The Core Image filter provided to an SKEffectNode must have a single inputImage parameter and produce a single outputImage parameter (in addition to other input parameters to control the actual effect)

    CILinearGradient does not have the inputImage and outputImage parameters, and hence cannot be used as an SKEffectNode filter.

    If you wish to use CILinearGradient, you need to create a custom CIFilter as follows:

    class SpriteKitGradient : CIFilter {
        var point0 = CIVector.init(x: 0, y: 0)
        var point1 = CIVector.init(x: 0, y: 0)
        var color0 = CIColor.black()
        var color1 = CIColor.white()
    
        var inputImage: CIImage?
    
        override var outputImage: CIImage? {
            guard let inputImage = inputImage else {
                return nil
            }
            let gradient = CIFilter(name: "CILinearGradient", withInputParameters:
                [
                    "inputPoint0" : point0,  "inputColor0" : color0,
                    "inputPoint1" : point1,  "inputColor1" : color1
                ]
            )
            let extent = inputImage.extent
            return gradient!.outputImage?.cropping(to: extent)
        }
    }
    
    class GameScene: SKScene {
    
        override func didMove(to view: SKView) {
    
            let node = SKSpriteNode(color: .green, size: CGSize(width: 100, height: 100))
    
            let gradient = SpriteKitGradient()
            gradient.color0 = CIColor.blue()
            gradient.color1 = CIColor.red()
            gradient.point0 = CIVector(x: 0, y: 0)
            gradient.point1 = CIVector(x: 200, y: 0)
    
    
            let effectNode = SKEffectNode()
            effectNode.filter = gradient
            effectNode.addChild(node)
    
            effectNode.position = CGPoint(x: 200, y: 200)
    
            addChild(effectNode)
    
        }
    }
    

    This should create:

    sample scene output