Realm complains 'Binary too big' - swift

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

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)

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.

Error when trying to save image in NSUserDefaults using Swift

When i try to save an image in NSUserDefaults, the app crashed with this error.
Why? Is it possible to save an image with NSUserDefaults? If not, then how do I save the image?
Image...
Code...
var image1:UIImage = image1
var save1: NSUserDefaults = NSUserDefaults.standardUserDefaults()
save1.setObject(Info.Image1, forKey: "Image1")
save1.synchronize()
Log error...
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
NSUserDefaults isn't just a big truck you can throw anything you want onto. It's a series of tubes which only specific types.
What you can save to NSUserDefaults:
NSData
NSString
NSNumber
NSDate
NSArray
NSDictionary
If you're trying to save anything else to NSUserDefaults, you typically need to archive it to an NSData object and store it (keeping in mind you'll have to unarchive it later when you need it back).
There are two ways to turn a UIImage object into data. There are functions for creating a PNG representation of the image or a JPEG representation of the image.
For the PNG:
let imageData = UIImagePNGRepresentation(yourImage)
For the JPEG:
let imageData = UIImageJPEGRepresentation(yourImage, 1.0)
where the second argument is a CGFloat representing the compression quality, with 0.0 being the lowest quality and 1.0 being the highest quality. Keep in mind that if you use JPEG, each time you compress and uncompress, if you're using anything but 1.0, you're going to degrade the quality over time. PNG is lossless so you won't degrade the image.
To get the image back out of the data object, there's an init method for UIImage to do this:
let yourImage = UIImage(data:imageData)
This method will work no matter how you converted the UIImage object to data.
In newer versions of Swift, the functions have been renamed, and reorganized, and are now invoked as:
For the PNG:
let imageData = yourImage.pngData()
For the JPEG:
let imageData = yourImage.jpegData(compressionQuality: 1.0)
Although Xcode will autocorrect the old versions for you
In order to save UIImage in NSUserDefaults, you need to convert UIImage into NSData using UIImageJPEGRepresentation(image, 1) then save it in NSUserDefaults. And on the other side, while retrieving it on another ViewController (within that same application) you need to get NSData and then convert it into UIImage.
Here i am writing small code snippet in swift to demonstrate this. I tried this in XCODE 6.4
/*Code to save UIImage in NSUserDefaults on viewWillDisappear() event*/
override func viewWillDisappear(animated: Bool)
{
super.viewWillDisappear(animated)
//Get some image in image variable of type UIImage.
var image = ....
let defaults = NSUserDefaults.standardUserDefaults()
var imgData = UIImageJPEGRepresentation(image, 1)
defaults.setObject(imgData, forKey: "image")
}
/*Code to get Image and show it on imageView at viewWillAppear() event*/
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
let defaults = NSUserDefaults.standardUserDefaults()
if let imgData = defaults.objectForKey("image") as? NSData
{
if let image = UIImage(data: imgData)
{
//set image in UIImageView imgSignature
self.imgSignature.image = image
//remove cache after fetching image data
defaults.removeObjectForKey("image")
}
}
}
Updated for Swift 3:
If you want to save image in UserDefault used below lines of code save and retrieve the image;
To Save image in UserDefault:
if let image = response.result.value {
UserDefaults.standard.register(defaults: ["key":UIImageJPEGRepresentation(image, 100)!])
UserDefaults.standard.set(UIImageJPEGRepresentation(image, 100), forKey: "key")
}
To Retrieve the image from UserDefault and set it to ImageView:
var mLogoImageView = UIImageView()
if let imageData = UserDefaults.standard.value(forKey: "key") as? Data{
let imageFromData = UIImage(data: imageData)
mLogoImageView.image = imageFromData!
}
Enjoy..!
In Swift 4 - 5
Set:
setImage(image: UIImage(named: "12")!)
func setImage(image : UIImage) {
UserDefaults.standard.set(image.jpegData(compressionQuality: 100), forKey: "key")
}
Get
func getImage() -> UIImage? {
if let imageData = UserDefaults.standard.value(forKey: "key") as? Data{
if let imageFromData = UIImage(data: imageData){
return imageFromData
}
}
return nil
}

NSData to UIImage

I'm trying to save a UIIMage and then retrieve it and display it. I've been successful by saving an image in the bundle, retrieving it from there and displaying. My problem comes from when I try to save an image to disk (converting it to NSData), then retrieving it. I convert the image to NSData like so...
NSData* topImageData = UIImageJPEGRepresentation(topImage, 1.0);
then I write it to disk like so...
[topImageData writeToFile:topPathToFile atomically:NO];
then I tried to retrieve it like so...
topSubView.image = [[UIImage alloc] initWithContentsOfFile:topPathToFile];
which returns no image (the size is zero). so then I tried...
NSData *data = [[NSData alloc] initWithContentsOfFile:topPathToFile];
topSubView.image = [[UIImage alloc] initWithData:data];
to no avail. When I step through the debugger I do see that data contains the correct number of bytes but I'm confused as to why my image is not being created. Am I missing a step? Do I need to do something with NSData before converting to an image?
Try this code. This worked for me.
Saving the data.
create path to save the image.
let libraryDirectory = NSSearchPathForDirectoriesInDomains(.libraryDirectory,
.userDomainMask,
true)[0]
let libraryURL = URL(fileURLWithPath: libraryDirectory, isDirectory: true)
let fileURL = libraryURL.appendingPathComponent("image.data")
convert the image to a Data object and save it to the file.
let data = UIImageJPEGRepresentation(myImage, 1.0)
try? data?.write(to: fileURL)
retrieving the saved image
let newImage = UIImage(contentsOfFile: fileURL.relativePath)
Create an imageview and load it with the retrieved image.
let imageView = UIImageView(image: newImage)
self.view.addSubview(imageView)
You should be able to use class methods of UIImage
[UIImage imageWithContentsOfFile:topPathToFile];
OR
[UIImage imageWithData:data];
Did that not work?
Hope this helps!
Just in case this helps someone, from iOS6 we have the imageWithData:scale method. To get an UIImage with the right scale from an NSData object, use that method, declaring the scale you use to store the original image. For example:
CGFloat screenScale = [[UIScreen mainScreen] scale];
UIImage *image = [UIImage imageWithData:myimage scale:screenScale];
Here, myimage is the NSData object where you stored the original image. In the example, I used the scale of the screen. If you use another scale for the original image, use that value instead.
Check this out: http://www.nixwire.com/getting-uiimage-to-work-with-nscoding-encodewithcoder/.
It has exactly what I think the solution to your problem is. You can't just make an image out of NSData, even though that's what common sense suggests. You have to use UIImagePNGRepresentation. Let me know if this helps.
Use if-let block with Data to prevent app crash & safe execution of code, as function UIImagePNGRepresentation returns an optional value.
if let img = UIImage(named: "TestImage.png") {
if let data:Data = UIImagePNGRepresentation(img) {
// Handle operations with data here...
}
}
Note: Data is Swift 3+ class. Use Data instead of NSData with
Swift 3+
Generic image operations (like png & jpg both):
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data:Data = UIImagePNGRepresentation(img) {
handleOperationWithData(data: data)
} else if let data:Data = UIImageJPEGRepresentation(img, 1.0) {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}
By using extension:
extension UIImage {
var pngRepresentationData: Data? {
return UIImagePNGRepresentation(img)
}
var jpegRepresentationData: Data? {
return UIImageJPEGRepresentation(self, 1.0)
}
}
*******
if let img = UIImage(named: "TestImage.png") { //UIImage(named: "TestImage.jpg")
if let data = img.pngRepresentationData {
handleOperationWithData(data: data)
} else if let data = img.jpegRepresentationData {
handleOperationWithData(data: data)
}
}
*******
func handleOperationWithData(data: Data) {
// Handle operations with data here...
if let image = UIImage(data: data) {
// Use image...
}
}