Why Can't I Invert My Image Back to Original with Cifilter in My Swift iOS App

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



Leave a reply



Submit