Why is my CIImage nil after I invert a UIImage? - swift

I have a very peculiar issue here where I convert a UIImage to CIImage after I inverted the UIImage. Here is the code:
let imageFromBuffer1 = self.imageInvert(image: self.imageFromBuffer)
let ciImage = CIImage(image: imageFromBuffer1)
So, in above code ciImage is nil for some bizarre reason. Can anybody explain this, or tell me what else to do to get the UIImage converted to CIImage?
This is the invert function I am using above:
func imageInvert(image: UIImage) -> UIImage{
let beginImage = CIImage(image: image)
var newImage = UIImage()
if let filter = CIFilter(name: "CIColorInvert") {
filter.setValue(beginImage, forKey: kCIInputImageKey)
let ciImage = filter.outputImage
newImage = UIImage(ciImage: ciImage!)
}
else{
print("filter does not exist")
newImage = image
}
return newImage
}

Because when an image is converted from one form to another some data may be modified, lost or not used. For instance when you load a UIImage from assets it may be larger than the view in which it is displayed, The graphics engine does it's best to display the image as true as possible. However some of the underlying image data may not be used, this is especially true for compressed jpeg images. Instead of converting the image back and forth consider transforming the current context the image resides in:
ViewController.swift
import UIKit
class ViewController: UIViewController {
lazy var imageView = UIImageView(frame: self.view.frame)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(imageView)
let tap = UITapGestureRecognizer(target: self, action: #selector(imageViewTapped))
imageView.addGestureRecognizer(tap)
imageView.isUserInteractionEnabled = true
imageView.contentMode = .scaleAspectFit
// https://en.wikipedia.org/wiki/File:Meisje_met_de_parel.jpg
imageView.image = UIImage(named: "Meisje_met_de_parel.jpg")
}
func invert(image: UIImage) -> UIImage? {
let ciImage = CIImage(image: image)
var newImage:UIImage? = nil
guard let filter = CIFilter(name: "CIColorInvert") else {return image}
filter.setValue(ciImage, forKey: kCIInputImageKey)
let context = CIContext(options: nil)
if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
newImage = UIImage(cgImage: cgImage)
}
return newImage
}
#objc func imageViewTapped() {
if let image = self.imageView.image {
let invertedImage = self.invert(image: image)
DispatchQueue.main.async {
self.imageView.image = invertedImage
}
}
}
}

Related

CIFilter can't be applied to SCNMaterial [duplicate]

This question already has an answer here:
Why can't I invert my image back to original with CIFilter in my Swift iOS app
(1 answer)
Closed 2 years ago.
Any CIFilter works fine when it's applied to UIImageView.
import UIKit
import CoreImage
#IBOutlet var imageView: UIImageView!
let ciBlurFilter = CIFilter(name: "CIGaussianBlur")!
func gaussianBlur() -> UIImage? {
let uiImage = UIImage(named: "texture.png")!
let ciImage = CIImage(image: uiImage)
ciBlurFilter.setValue(ciImage, forKey: "inputImage")
let resultedImage = ciBlurFilter.value(forKey: "outputImage") as! CIImage
let blurredImage = UIImage(ciImage: resultedImage)
return blurredImage
}
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = self.gaussianBlur()
}
But it doesn't work if it's applied to SceneKit's material:
import SceneKit
#IBOutlet var sceneView: SCNView!
let ciBlurFilter = CIFilter(name: "CIGaussianBlur")!
func gaussianBlur() -> UIImage? {
let uiImage = UIImage(named: "texture.png")!
let ciImage = CIImage(image: uiImage)
ciBlurFilter.setValue(ciImage, forKey: "inputImage")
let resultedImage = ciBlurFilter.value(forKey: "outputImage") as! CIImage
let blurredImage = UIImage(ciImage: resultedImage)
return blurredImage
}
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene()
let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.1))
sphereNode.geometry?.firstMaterial?.diffuse.contents = self.gaussianBlur()
sceneView.scene?.rootNode.addChildNode(sphereNode)
}
Why SCNMaterial with CIFilter is invisible (although it supports UIImages)?
What's the matter?
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.

Converting UIImage to CIImage Returns nil

I'm trying to convert the UIImage from an imageView into a CIImage for the purpose of filtering it. However, I cannot get the CIImage to have a value.
In the simplest form, here's what I am trying:
let ciInput = CIImage(image: imageView.image!)
but the ciInput always is nil.
I have also tried
let ciInput = CIImage(cgImage: imageView.image!.cgImage)
but also returns nil.
(imageView.image is not nil, but imageView.image!.cgImage and imageView.image!.ciImage are both nil)
I need to convert the UIImage from the imageView into a valid CIImage. Any help is appreciated, thanks!
EDIT: Here is the full function code
func makeWhiteTransparent(imageView: UIImageView) {
let invertFilter = CIFilter(name: "CIColorInvert")
let ciContext = CIContext(options: nil)
let ciInput = CIImage(image: imageView.image!) //This is nil
invertFilter?.setValue(ciInput, forKey: "inputImage")
let ciOutput = invertFilter?.outputImage
let cgImage = ciContext.createCGImage(ciOutput!, from: (ciOutput?.extent)!)
imageView.image = UIImage(cgImage: cgImage!)
}
When running this function, I get a fatal unwrapping nil error on the last line. Using the debugger, I discovered that the ciInput is nil, which it should not be.
EDIT 2:
The image on the imageView before calling makeWhiteTransparent is a QR code generated with this function:
func generateQRCode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 12, y: 12)
if let output = filter.outputImage?.applying(transform) {
return UIImage(ciImage: output)
}
}
return nil
}
So the problem was in my QR Code generation. The code returned a UIImage from a CIImage without properly utilizing CGContext to return the UIImage. Here is the corrected QR Code function that fixed the issue.
func generateQRCode(from string: String) -> UIImage? {
let data = string.data(using: String.Encoding.ascii)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: 12, y: 12)
if let output = filter.outputImage?.applying(transform) {
let context = CIContext()
let cgImage = context.createCGImage(output, from: output.extent)
return UIImage(cgImage: cgImage!)
}
}
return nil
}

PNG/JPEG representation from CIImage always returns nil

I'm currently making a photo editing app.
When a photo is selected by the user, it is automatically converted into black and white using this code:
func blackWhiteImage(image: UIImage) -> Data {
print("Starting black & white")
let orgImg = CIImage(image: image)
let bnwImg = orgImg?.applyingFilter("CIColorControls", withInputParameters: [kCIInputSaturationKey:0.0])
let outputImage = UIImage(ciImage: bnwImg!)
print("Black & white complete")
return UIImagePNGRepresentation(outputImage)!
}
The problem I am having with this code is that I keep getting this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I have had my code in a slightly different configuration, but it still breaks when it gets to the UIImagePNG/JPEGRepresentation(xx) section.
Are there any ways to get the PNG or JPEG data from a CIImage for use in an image view / just UIImage in general?
Any of the other methods don't go into enough detail for what code should be used.
Just begin a new graphics context and draw your grayscale image there. iOS 10 or later you can use UIGraphicsImageRenderer, for older iOS version syntax please check edit history:
Xcode 11 • Swift 5.1
func blackWhiteImage(image: UIImage, isOpaque: Bool = false) -> Data? {
guard let ciImage = CIImage(image: image)?.applyingFilter("CIColorControls", parameters: [kCIInputSaturationKey: 0]) else { return nil }
let format = image.imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: image.size, format: format).image { _ in
UIImage(ciImage: ciImage).draw(in: CGRect(origin: .zero, size: image.size))
}.pngData()
}
You can also extend UIImage to return a grayscale image :
extension UIImage {
var coreImage: CIImage? { CIImage(image: self) }
func grayscale(isOpaque: Bool = false) -> UIImage? {
guard let coreImage = coreImage?.applyingFilter("CIColorControls", parameters: [kCIInputSaturationKey: 0]) else { return nil }
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: size, format: format).image { _ in
UIImage(ciImage: coreImage).draw(in: CGRect(origin: .zero, size: size))
}
}
}
let profilePicture = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
if let grayscale = profilePicture.grayscale(), let data = grayscale.pngData() { // or Swift 4.1 or earlier -> let data = UIImagePNGRepresentation(grayscale)
print(data.count) // 689035
}

Core Image and memory leak, swift 3.0

i have problem i try use filter at some images which have extension 3000x2000 , when i do it RAM upper and app have fatal error the "didReceiveMemoryWarning".
func setFilters(images: [UIImage]) -> [UIImage] {
let filter = CIFilter(name: "CIColorControls")!
filter.setValue(2.0, forKey: kCIInputContrastKey)
let context = CIContext(options: nil)
var result = [UIImage]()
for img in images {
let newImage = autoreleasepool(invoking: { () -> UIImage in
filter.setValue(CIImage(image: img)!, forKey: kCIInputImageKey)
let ciImage = filter.outputImage!
let cgImage = context.createCGImage(ciImage, from: ciImage.extent)
return UIImage(cgImage: cgImage!, scale: img.scale, orientation: img.imageOrientation)
})
result.append(newImage)
}
return result
}
It's not a memory leak; it's that you are in fact using too much memory. And it's not the use of CIFilter that's causing the problem; it's the fact that you are trying to keep all of these huge UIImage objects in memory in a single array:
var result = [UIImage]()
// ...
result.append(newImage)
Don't do that.

Image roates after CIFilter applied

I'm applying a CIFilter to an image but it gets rotated after the filter is applied. I found a solution here in objective-c, how could I do this in swift? My code is below.
let originalImage = CIImage(image: cameraStill.image)
var filter = CIFilter(name: "CIPhotoEffect"+x)
filter.setDefaults()
filter.setValue(originalImage, forKey: kCIInputImageKey)
var outputImage = filter.outputImage
var newImage = UIImage(CIImage: outputImage)
v.setImage(newImage, forState: .Normal)
The code below worked fine for me:
var image = UIImage(named: "Turtle")
let ciImage = CIImage(image: image)
let filter = CIFilter(name: "CISepiaTone")
filter.setValue(ciImage, forKey: kCIInputImageKey)
filter.setValue(1.5, forKey: kCIInputIntensityKey)
let newImage = UIImage(CIImage: filter.outputImage)
let imageView = UIImageView(image: newImage)
self.view.addSubview(imageView)
Let me know how you go.