Core Image and memory leak, swift 3.0 - swift

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.

Related

Generate a thumbnail from UIImage in Swift

I would like to take a UIImage and output a 100 by 100 version of it to use as a thumbnail. I found some answers on SO for how to do this in objective-C but not swift and wasn't sure where to start. I also found the link (https://nshipster.com/image-resizing/#technique-3-creating-a-thumbnail-with-image-io) which suggests it isn't as straight forward as I would have hoped. That link had me hopeful that one of the approaches may work, but each references a URL argument which confused me since I am starting with a UIImage as the input.
In a a similar situation (user uploading a picture from phone) I use the code below to create a thumbnail from the asset, I am looking for help doing the same thing when the input is a UIImage instead of a PHAsset.
func getAssetThumbnail(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
manager.requestImage(for: asset, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
thumbnail = result!
})
return thumbnail
}
iOS 15 added the following beta APIs to UIImage.
func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)
func preparingThumbnail(of: CGSize) -> UIImage?
https://developer.apple.com/documentation/uikit/uiimage/
Tested the code, and this works fine for me:- (Swift 5.0)
let yourImage = UIImage()
if let imageData = yourImage.pngData(){
let options = [
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: 100] as CFDictionary // Specify your desired size at kCGImageSourceThumbnailMaxPixelSize. I've specified 100 as per your question
imageData.withUnsafeBytes { ptr in
guard let bytes = ptr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, imageData.count){
let source = CGImageSourceCreateWithData(cfData, nil)!
let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options)!
let thumbnail = UIImage(cgImage: imageReference) // You get your thumbail here
}
}
}
For future reference, I've just come across the same issue and this thread has some nice solutions: Creating a thumbnail from UIImage using CGImageSourceCreateThumbnailAtIndex
I went with this, which is working nicely in Swift 5.3:
let uiImage = someUIImage
let options = [
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceThumbnailMaxPixelSize: 100] as CFDictionary
guard let imageData = uiImage.pngData(),
let imageSource = CGImageSourceCreateWithData(imageData as NSData, nil),
let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options)
else {
return nil
}
return UIImage(cgImage: image)

Why is my CIImage nil after I invert a UIImage?

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
}
}
}
}

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
}

Realm complains 'Binary too big'

I want to save image in Realm but it says that binary is too big. I know that NSData should be less than 16MB. So how can I handle this issue? Anyway to resize NSData?
Had the same problem as well, doing research I fixed my error with help from Realm docs. Here is the link. https://realm.io/docs/tutorials/scanner/#overview.
The helpful code snippet:
func data() -> Data {
var imageData = UIImagePNGRepresentation(self)
// Resize the image if it exceeds the 2MB API limit
if (imageData?.count)! > 2097152 {
let oldSize = self.size
let newSize = CGSize(width: 800, height: oldSize.height / oldSize.width *
800)
let newImage = self.resizeImage(self, size: newSize)
imageData = UIImageJPEGRepresentation(newImage, 0.7)
}
return imageData!
}
To add to Realm, A code can be something like this:
#IBOutlet weak var thumbImg: UIImageView!
let picture = Image()
let imageDownSizing = thumbImg.image?.data()
//thumbImg.image is of type UIImage type, so convert UIImage -> Data.
//picture.image is of type Data.
picture.image = UIImagePNGRepresentation(thumbImg.image!)
picture.image = imageDownSizing
let item = Item()
item.toImage = picture
do{
let realm = try! Realm()
try realm.write {
realm.add(item)
}
}catch{
print("Error saving context \(error)")
}
You can reference parts of the file with NSFileHandle and it's offsetInFile method. e.g. in 16MB chunks.
You can use let imageData = image?.jpegData(compressionQuality: 0.5)
which will get your image in Data format and compress the size