Why can't I invert my image back to original with CIFilter in my Swift iOS app
The problem is this line:
imageView.image = UIImage(CIImage: (filter.outputImage)!)
You can't do that. A CIImage does not magically turn into a UIImage. An image view needs a bitmap-based image, not a CIImage-based image. You have to render the CIImage to turn it into a UIImage, either by getting a CIContext and telling it to create CGImage or by drawing the CIImage into a graphics context.
CIColorInvert and CIColorMatrix does not invert image properly
The problem was in working color space. I found answer to my question here. The CIContext
which I creates looks now as follwing:
var ciContext = CIContext(options: [
kCIImageColorSpace: NSNull(),
kCIImageProperties: NSNull(),
kCIContextWorkingColorSpace: NSNull()
])
To be honest, I do not know exactly the background, so I am not sure, why it is not working without setting WorkingColorSpace
to NSNull
. If someone knows, I would be really thankful for explanation.
imageView.image = UIImage(ciImage: ) won't update a second time
Quick searching ... seems to be either a "bug" or a change...
Changing your generator code to this seems to correct the issue:
func generateQRCode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
let context = CIContext()
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 3, y: 3)
if let output = filter.outputImage?.transformed(by: transform) {
if let retImg = context.createCGImage(output, from: output.extent) {
return UIImage(cgImage: retImg)
}
}
}
return nil
}
func generateBarcode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
let context = CIContext()
if let filter = CIFilter(name: "CICode128BarcodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 3, y: 3)
if let output = filter.outputImage?.transformed(by: transform) {
if let retImg = context.createCGImage(output, from: output.extent) {
return UIImage(cgImage: retImg)
}
}
}
return nil
}
CIFilter can't be applied to SCNMaterial
The UIImage
you create with that constructor is not actually rendered at that moment. The receiver of the image needs to know that the image needs to be rendered before use, which is seemingly not handled by SceneKit.
Please see my answer here for details.
Here's how you render the CIImage
in Swift:
// ideally you create this once and re-use it;
// you should not create a new context for every draw call
let ciContext = CIContext()
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent)
let uiImage = cgImage.flatMap({ UIImage.init(cgImage: $0) })
You can pass the CGImage
to the material or wrap it into a UIImage
, both should work.
Using custom CIFilter on CALayer shows no change to CALayer
As @DonMag points out it should have worked with the changes he described. How ever unfortunately we heard back from Apple today;
At this time, there is a bug preventing custom CIFilters on a CALayer from working. There is no workaround for this bug at this time.
When we file the bug I will add the link here for those interested. But at this time you can not add a custom CIFilter to a CALayer on macOS 11.
Let’s hope they fix it for all of you reading this for a solution.
EDIT:
So bad news... currently on macOS 12.2.1, and it still has the same issue, nothing has happened based on our ticket. Doesn't seem like Apple want's to fix this. For those of you out there looking: This still does NOT work on a CALayer
even with all the options on like described in the other answers. A builtin CIFilter
works as expected.
Note that using the same custom CIFilter
on a CALayer
for an export using AVVideoCompositionCoreAnimationTool
does work!
CIFilter output image nil
You cannot call UIImage(CIImage:)
and use that UIImage as the image of a UIImageView. UIImageView requires a UIImage backed by a bitmap (CGImage). A UIImage instantiated with CIImage has no bitmap; it has no actual image, it's just a set of instructions for applying a filter. That is why your UIImageView's image is nil.
How to convert this UIGraphics image operation to Core Image?
I think you are on the right track.
You can create the left half from the right half by applying transformations to it using let leftHalf = rightHalf.transformed(by: transformation)
. The transformation should mirror it and translate it to the correct position, i.e., next to the right half.
You can them combine the two into one image using let result = leftHalf.composited(over: rightHalf)
and render that result using a CIContext
.
See implementation of some default CIFilter's?
Though you found the bug yourself, here is an addition to your solution:
Instead of func outputImage() -> CIImage? { ... }
you should override the existing property of CIFilter
since it is the standard way of getting a filter's output:
override var outputImage: CIImage? {
// your computation here
}
And one more tip: For this kind of kernel you can use a CIColorKernel
since you only need to sample single pixels without needing to look at their neighbors. The kernel would then look like this:
float4 fadeTransition(sample_t foreground, sample_t background, float time) {
return float4(mix(foreground.rgb, background.rgb, background.a * float(time)), foreground.a);
}
And then initialize the kernel as CIColorKernel
instead of CIKernel
.
Speaking of which, you also don't need to initialize a new kernel per filter instance but use one static kernel instead. (This was recommended by Apple engineers when I talked to them during a WWDC Lab). This is how the whole filter would look like then:
class TestTransitionFilter: CIFilter {
@objc dynamic var inputImage: CIImage?
@objc dynamic var inputImage2: CIImage?
@objc dynamic var inputTime: CGFloat = 0
private static var kernel: CIColorKernel {
let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
return try! CIColorKernel(functionName: "fadeTransition", fromMetalLibraryData: data)
}
override var outputImage: CIImage? {
guard let inputImage = inputImage, let inputImage2 = inputImage2 else {return nil}
return Self.kernel.apply(extent: inputImage.extent, arguments: [inputImage, inputImage2, inputTime])
}
}
Related Topics
Firebase .Indexon Dynamic Keys
Maximum Height of iOS 8 Today Extension
How to Bring Application to Foreground in iOS
Itsappusesnonexemptencryption Export Compliance While Internal Testing
Getting the Value of a Uitextfield as Keystrokes Are Entered
Simulator Slow-Motion Animations Are Now On
Storyboards VS. the Old Xib Way
How to Save Nsmutablearray or Nsdictionary Data as File in iOS
Enable or Disable iPhone Push Notifications Inside the App
Using Autolayout in a Tableheaderview
Swift - Image Data from Ciimage Qr Code/How to Render Cifilter Output
Disabling Nslog for Production in Swift Project
Rotating a View in Layoutsubviews
Dashed Line Border Around Uiview
iPhone Sdk:How to Play Video Inside a View? Rather Than Fullscreen
Convert Uiimage to Nsdata and Convert Back to Uiimage in Swift
iOS Designated Initializers:Using Ns_Designated_Initializer
Navigating to a New Screen When Stream Value in Bloc Changes