How do you subclass CIFilter now? In Swift 3 I could do this as a simple example:
class CustomFilter: CIFilter {
var inputImage: CIImage?
var inputOrigin: CIVector?
var inputAnotherVar: String?
}
But in Swift 4 I get an NSException. If I remove "input" from each variable it works fine. I could just do that. But I feel like I'm missing something important and I can't seem to find anything explaining this behaviour.
This compiles fine in Swift4:
class CustomFilter: CIFilter {
var image: CIImage?
var origin: CIVector?
var anotherVar: String?
}
Here's the error in a Playground:
Based on the comments, here's some Swift 4 code (unchanged from Swift 3) that both builds and executes. I'm not seeing where your issue is, so if this doesn't help you out, comment on it and I'll delete it. (If it does help, I'll edit my answer to be more specific!)
The first CIFilter
uses CIColorInvert
and CIHeightFieldFromMask
to create a "text mask" based on the text in a UILabel
. It also overrides
the outputImage
property of CIFilter
. The second CIFilter
is actually a "wrapper" around a CIKernel
, using an CIImage
as inputImage
, the mask (from the first filter) as inputMask
and also overrides outputImage
like the first does.
Virtually all of this code was lifted from Core Image for Swift by Simon Gladman, nowadays available as an iBook for free. While the book was written in Swift 2, I find it to be a valuable resource for working with Core Image.
(Side note: The book combines all of this. I split out things as I was working on adapting this into an existing app as a watermark. I ended up going a different route!)
Mask.swift
public class Mask: CIFilter {
public var inputExtent:CGRect?
var inputRadius: Float = 15 {
didSet {
if oldValue != inputRadius {
refractingImage = nil
}
}
}
private var refractingImage: CIImage?
private var rawTextImage: CIImage?
override public var outputImage: CIImage! {
if refractingImage == nil {
generateRefractingImage()
}
let mask = refractingImage?.applyingFilter("CIColorInvert", parameters: [:])
return mask
}
func generateRefractingImage() {
let label = UILabel(frame: inputExtent!)
label.text = "grand canyon"
label.font = UIFont.boldSystemFont(ofSize: 300)
label.adjustsFontSizeToFitWidth = true
label.textColor = UIColor.white
UIGraphicsBeginImageContextWithOptions(
CGSize(width: label.frame.width,
height: label.frame.height), true, 1)
label.layer.render(in: UIGraphicsGetCurrentContext()!)
let textImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
rawTextImage = CIImage(image: textImage!)!
refractingImage = CIFilter(name: "CIHeightFieldFromMask",
withInputParameters: [
kCIInputRadiusKey: inputRadius,
kCIInputImageKey: rawTextImage!])?.outputImage?
.cropped(to: inputExtent!)
}
}
Refraction.swift
public class Refraction: CIFilter {
public var inputImage: CIImage?
public var inputMask:CIImage?
var inputRefractiveIndex: Float = 4.0
var inputLensScale: Float = 50
public var inputLightingAmount: Float = 1.5
var inputLensBlur: CGFloat = 0
public var inputBackgroundBlur: CGFloat = 2
var inputRadius: Float = 15
override public func setDefaults()
{
inputRefractiveIndex = 4.0
inputLensScale = 50
inputLightingAmount = 1.5
inputRadius = 15
inputLensBlur = 0
inputBackgroundBlur = 2
}
override public var outputImage: CIImage! {
guard let inputImage = inputImage, let refractingKernel = refractingKernel else {
return nil
}
let extent = inputImage.extent
let arguments = [inputImage,
inputMask!,
inputRefractiveIndex,
inputLensScale,
inputLightingAmount] as [Any]
return refractingKernel.apply(extent: extent,
roiCallback: {
(index, rect) in
return rect
},
arguments: arguments)!
}
let refractingKernel = CIKernel(source:
"float lumaAtOffset(sampler source, vec2 origin, vec2 offset)" +
"{" +
" vec3 pixel = sample(source, samplerTransform(source, origin + offset)).rgb;" +
" float luma = dot(pixel, vec3(0.2126, 0.7152, 0.0722));" +
" return luma;" +
"}" +
"kernel vec4 lumaBasedRefract(sampler image, sampler refractingImage, float refractiveIndex, float lensScale, float lightingAmount) \n" +
"{ " +
" vec2 d = destCoord();" +
" float northLuma = lumaAtOffset(refractingImage, d, vec2(0.0, -1.0));" +
" float southLuma = lumaAtOffset(refractingImage, d, vec2(0.0, 1.0));" +
" float westLuma = lumaAtOffset(refractingImage, d, vec2(-1.0, 0.0));" +
" float eastLuma = lumaAtOffset(refractingImage, d, vec2(1.0, 0.0));" +
" vec3 lensNormal = normalize(vec3((eastLuma - westLuma), (southLuma - northLuma), 1.0));" +
" vec3 refractVector = refract(vec3(0.0, 0.0, 1.0), lensNormal, refractiveIndex) * lensScale; " +
" vec3 outputPixel = sample(image, samplerTransform(image, d + refractVector.xy)).rgb;" +
" outputPixel += (northLuma - southLuma) * lightingAmount ;" +
" outputPixel += (eastLuma - westLuma) * lightingAmount ;" +
" return vec4(outputPixel, 1.0);" +
"}"
)
}
Usage
let filterMask = Mask()
let filter = Refraction()
var imgOriginal:CIImage!
var imgMask:CIImage!
var imgEdited:CIImage!
// I have a set of sliders that update a tuple and send an action that executes the following code
filterMask.inputRadius = sliders.valuePCP.3
imgMask = filterMask.outputImage
filter.inputMask = imgMask
filter.inputRefractiveIndex = sliders.valuePCP.0
filter.inputLensScale = sliders.valuePCP.1
filter.inputLightingAmount = sliders.valuePCP.2
imgEdited = filter.outputImage
Hope this helps!
I ran into this issue (same "error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION...") in Swift 4 while experimenting with Simon Gladman's "Core Image for Swift." I also tried running example code in an app instead of a playground. The solution for me was to add an @objc dynamic
in front of var inputImage: CIImage?
In your code it would look like this:
class CustomFilter: CIFilter {
@objc dynamic var inputImage: CIImage?
var inputOrigin: CIVector?
var inputAnotherVar: String?
}
As I understand it, this is because Swift 4 by default minimizes inference so as to reduce binary code size. In contrast, Swift 3 implicitly infers Objc attributes. What this means in practice, is I have to add the @objc dynamic
to certain variables that will take advantage of Objective-C's dynamic dispatch, such as when setting a CoreImage filter: filter.setValue(inputImage, forKey: kCIInputImageKey)
. Here are some resources that describe similar issues with Swift's static dispatch using Swift4 when dealing with Obj-C API's that rely on dynamic dispatch and how to deal with dispatch when you migrate from Swift 3 to 4.