Uiview to Data (Saving a Gif to firebase) - swift

In my app, I take a photo from my camera, and then the user can add a sticker gif on it.
I want to save the result on Firebase Storage (in a gif format)
So to summarize, I want to convert my UIview into Data, to save it on Firebase ( Storage.storage().reference().child("Gif").putData(data, metadata: nil, completion: { (metadata, error) in })
I have tried that :
let yourViewToData = NSKeyedArchiver.archivedData(withRootObject: self.canvasView)
but the result isn't a gif file
I have tried a func like this but I cannot intercept Data on it and I'm not sure of the result :
let img = self.canvasView.toImage()
animatedGif(from: [img])
func animatedGif(from images: [UIImage]) {
let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] as CFDictionary
let frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [(kCGImagePropertyGIFDelayTime as String): 1.0]] as CFDictionary
let documentsDirectoryURL: URL? = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL: URL? = documentsDirectoryURL?.appendingPathComponent("animated.gif")
if let url = fileURL as CFURL? {
if let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, images.count, nil) {
CGImageDestinationSetProperties(destination, fileProperties)
for image in images {
if let cgImage = image.cgImage {
CGImageDestinationAddImage(destination, cgImage, frameProperties)
}
}
if !CGImageDestinationFinalize(destination) {
print("Failed to finalize the image destination")
}
print("Url = \(fileURL)")
}
}
}
Any idea how I can save my UIView to a gif file on Firebase Storage?
I am looking to make an alternative solution . Take like 15 screenshot of UImage from my UIView, in order to make a gif later. I have this func :
func toImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImageFromMyView!
}
Does someone know how can I make like 15 screenshot with this func? Thanks
Thanks !

Related

Swift. How to extract the individual sub images from an HEIC image

I need to be able to load an heic image and extract and output all of the sub images as pngs similar to how preview does it. For example, if you open a dynamic heic wallpaper in preview, it shows all the images in the sidebar with their names:
How do you do this? I've tried to use NSImage like below. But that only outputs a single image:
let image = NSImage(byReferencing: url)
image.writePNG(toURL: newUrl)
You need to load the HEIC data, get its CGImageSource and its count. Then create a loop from 0 to count-1 and get each image at the corresponding index. You can create an array with those CGImages in memory or write them to disk (preferred). Note that this will take a while to be executed because of the size of the HEIC file 186MB. Each image extracted will be from 19MB to 28MB.
func extractHeicImages(from url: URL) throws {
let data = try Data(contentsOf: url)
let location = url.deletingLastPathComponent()
let pathExtension = url.pathExtension
let fileName = url.deletingPathExtension().lastPathComponent
let destinationFolder = location.appendingPathComponent(fileName)
guard pathExtension == "heic", let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { return }
let count = CGImageSourceGetCount(imageSource)
try FileManager.default.createDirectory(at: destinationFolder, withIntermediateDirectories: false, attributes: nil)
for index in 0..<count {
try autoreleasepool {
if let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, nil) {
let number = String(format: "#%05d", index)
let destinationURL = destinationFolder
.appendingPathComponent(fileName+number)
.appendingPathExtension(pathExtension)
try NSImage(cgImage: cgImage, size: .init(width: cgImage.width, height: cgImage.height))
.heic?
.write(to: destinationURL)
print("saved image " + number)
}
}
}
}
You will need these helpers as well to extract the cgimate from your image and also to get a HEIC data representation from them:
extension NSImage {
var heic: Data? { heic() }
var cgImage: CGImage? {
var rect = NSRect(origin: .zero, size: size)
return cgImage(forProposedRect: &rect, context: .current, hints: nil)
}
func heic(compressionQuality: CGFloat = 1) -> Data? {
guard
let mutableData = CFDataCreateMutable(nil, 0),
let destination = CGImageDestinationCreateWithData(mutableData, "public.heic" as CFString, 1, nil),
let cgImage = cgImage
else { return nil }
CGImageDestinationAddImage(destination, cgImage, [kCGImageDestinationLossyCompressionQuality: compressionQuality] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
Playground testing. This assumes the "Catalina.heic" is located at your desktop.
let catalinaURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("Catalina.heic")
do {
try extractHeicImages(from: catalinaURL)
} catch {
print(error)
}
Each subimage is represented by a NSBitmapImageRep. Loop the image reps, convert to png and save:
let imageReps = image.representations
for imageIndex in 0..<imageReps.count {
if let imageRep = imageReps[imageIndex] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(imageIndex).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}
The conversion to png takes some time. Running the conversions in parallel is faster but I'm not sure if it's save:
DispatchQueue.concurrentPerform(iterations: imageReps.count) { iteration in
if let imageRep = imageReps[iteration] as? NSBitmapImageRep {
if let data = imageRep.representation(using: .png, properties: [:]) {
do {
let url = folderURL.appendingPathComponent("image \(iteration).png", isDirectory: false)
try data.write(to: url, options:[])
} catch {
print("Unexpected error: \(error).")
}
}
}
}

Save GIF Image in Photo Gallery from UIImageView

I have UIImageView which is displaying GIF image, now I am unable to save GIF image into photo gallery.
I am using below swift code
guard let image = imageView.image else { return }
UIImageWriteToSavedPhotosAlbum(image,
self,
#selector(savedImage),
nil);
When I use above code it only save single image in photo gallery not saving as GIF.
Suggestion required, how to save GIF loaded from URL or available in the UIImageView into Gallery.
First your need to get all images of gif from imageView and the duration of the image, now you can generate the gif and save in document directory. Finally, save the file from URL to Photo Album
import ImageIO
import Photos
import MobileCoreServices
#IBAction func btnSaveGifFilePressed(_ sender: Any) {
if let imgs = self.imgView.image?.images, imgs.count > 0 {
var animationDuration = self.imgView.image?.duration ?? 1.0
animationDuration = animationDuration / Double(imgs.count);
let url = FileManager.default.urlForFile("lion.gif")
FileManager.default.createGIF(with: imgs, url: url, frameDelay: animationDuration)
PHPhotoLibrary.shared().performChanges ({
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
}) { saved, error in
if saved {
print("Your image was successfully saved")
}
}
}
}
Create GIF using the following code.
func createGIF(with images: [UIImage], url: URL, frameDelay: Double) {
if let destinationGIF = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeGIF, images.count, nil) {
let properties = [
(kCGImagePropertyGIFDictionary as String): [(kCGImagePropertyGIFDelayTime as String): frameDelay]
]
for img in images {
let cgImage = img.cgImage
CGImageDestinationAddImage(destinationGIF, cgImage!, properties as CFDictionary?)
}
CGImageDestinationFinalize(destinationGIF)
}
}
Get a URL in single line:
func urlForFile(_ fileName: String, folderName: String? = nil) -> URL {
var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
if let folder = folderName {
documentsDirectory = documentsDirectory.appendingPathComponent(folder)
if !self.fileExists(atPath: documentsDirectory.path) {
try? self.createDirectory(atPath: documentsDirectory.path, withIntermediateDirectories: true, attributes: nil)
}
}
let fileURL = documentsDirectory.appendingPathComponent(fileName)
return fileURL;
}

How to save/read an image to/from the iPhone device storage?

I am going to save an image into the iphone device and read from it.
So I had written like this for this function.
/*
* save image to local device
*/
func saveImageToLocal(image: UIImage, fileName: String) {
if let data = UIImagePNGRepresentation(image) {
let filePath = self.getDocumentsDirectory().appendingPathComponent(fileName)
try? data.write(to: filePath, options: .atomic)
}
}
/*
* get image from local device
*/
func getImageFromLocal(fileName:String) -> UIImage? {
if let filePath = "\(self.getDocumentsDirectory())\(fileName)" as String! {
if let image = UIImage(contentsOfFile: filePath) {
return image
}
}
return nil
}
/*
* get document directory to save/read local file
*/
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
But it didn't work correctly.
Which mistake do I have for this?
How can I implement this?
Regards.
You have the problem in getting filePath inside your getImageFromLocal(fileName:) -> UIImage? method. Change this according to your saveImageToLocal(image:) method as like below:
func getImageFromLocal(fileName:String) -> UIImage? {
let filePath = self.getDocumentsDirectory().appendingPathComponent(fileName)
if let image = UIImage(contentsOfFile: filePath.path) {
return image
}
return nil
}

Save image in Realm

I'm trying to pick image from device's Photo Library in method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
userPhoto.image = info[UIImagePickerControllerOriginalImage] as! UIImage?
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
dismiss(animated: true, completion: nil)
}
and save this picture in Realm (as NSData):
asset.assetImage = UIImagePNGRepresentation(userPhoto.image!)! as NSData?
...
try! myRealm.write
{
user.assetsList.append(asset)
myRealm.add(user)
}
After build succeeded and trying to pick and save image (in the app) i have app error:
'Binary too big'
What i'm doing wrong?
P.S. Sorry for my English :)
After some actions i have this code. But it's overwrite my image.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
let imageUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageName = imageUrl.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let photoURL = NSURL(fileURLWithPath: documentDirectory)
let localPath = photoURL.appendingPathComponent(imageName!)
let image = info[UIImagePickerControllerOriginalImage]as! UIImage
let data = UIImagePNGRepresentation(image)
do
{
try data?.write(to: localPath!, options: Data.WritingOptions.atomic)
}
catch
{
// Catch exception here and act accordingly
}
userPhoto.image = image
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
urlCatch = (localPath?.path)!
self.dismiss(animated: true, completion: nil);
}
Don't save the image itself into realm, just save the location of the image into realm as String or NSString and load the image from that saved path. Performance wise it's always better to load images from that physical location and your database doesn't get too big
func loadImageFromPath(_ path: NSString) -> UIImage? {
let image = UIImage(contentsOfFile: path as String)
if image == nil {
return UIImage()
} else{
return image
}
}
or you just save the image name, if it's in your documents directory anyhow
func loadImageFromName(_ imgName: String) -> UIImage? {
guard imgName.characters.count > 0 else {
print("ERROR: No image name")
return UIImage()
}
let imgPath = Utils.getDocumentsDirectory().appendingPathComponent(imgName)
let image = ImageUtils.loadImageFromPath(imgPath as NSString)
return image
}
and here a rough example how to save a captured image to your directory with a unique name:
#IBAction func capture(_ sender: AnyObject) {
let videoConnection = stillImageOutput?.connection(withMediaType: AVMediaTypeVideo)
stillImageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
//self.stillImage = UIImage(data: imageData!)
//self.savedImage.image = self.stillImage
let timestampFilename = String(Int(Date().timeIntervalSince1970)) + "someName.png"
let filenamePath = URL(fileReferenceLiteralResourceName: getDocumentsDirectory().appendingPathComponent(timestampFilename))
let imgData = try! imageData?.write(to: filenamePath, options: [])
})
/* helper get Document Directory */
class func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
//print("Path: \(documentsDirectory)")
return documentsDirectory as NSString
}
https://realm.io/docs/objc/latest/#current-limitations
maximum data size is 16 MB . this is limitation of realm
Depending on how your serializing the image data (for example if it's a lossless bitmap), it's quite possible that this data exceed 16MB, which as you've stated is Realm's maximum supported size for binary properties.
When you invoke NSData.length, how large does it say your data is?

How to Handle Image Orientation

I have an issue on capture image from camera. When I capture the image from camera using UIImagePickerController and after Capturing I save the image in my document directory and display it on my custom collectionView. But the problem is when I capture image and save it to document directory and when I load the image in my collectionView it display for the user on wrong orientation how can I handle the image orientation? I tried to save image as JPEG its work good but it support only one orientation. I want to save image as png because it support all orientation.
My code:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
// handle image orientation
//end
// code for save image in document directory from camera roll
let fileManager = NSFileManager.defaultManager()
do {
let document = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let getFolders = try fileManager.contentsOfDirectoryAtURL(document, includingPropertiesForKeys: nil, options: .SkipsHiddenFiles)
var maxImageNumber:Int = 0 // for get the max number image in document directory
for folder in getFolders {
if folder.lastPathComponent! == albumName {
let getImagesCheck = try fileManager.contentsOfDirectoryAtURL(folder, includingPropertiesForKeys: nil, options: .SkipsHiddenFiles)
let getImages = try fileManager.contentsOfDirectoryAtURL(folder, includingPropertiesForKeys: nil, options: .SkipsHiddenFiles)
if getImages.count <= 0 {
let imageUrl = folder.URLByAppendingPathComponent("Image \(getImagesCheck.count + 1).png")
if let convertImage = UIImagePNGRepresentation(image) {
convertImage.writeToURL(imageUrl, atomically: true)
}
}else {
// continue save images
let getImages_else = try fileManager.contentsOfDirectoryAtURL(folder, includingPropertiesForKeys: nil, options: .SkipsHiddenFiles)
for img in getImages_else {
let getImageName = img.lastPathComponent!
let arrayOne = getImageName.componentsSeparatedByString(".")
let arrayTwo = arrayOne[0].componentsSeparatedByString(" ")
let getImageNumber = Int(arrayTwo[1])
if getImageNumber! > maxImageNumber {
maxImageNumber = getImageNumber!
}
}
let imageUrl = folder.URLByAppendingPathComponent("Image \(maxImageNumber + 1).png")
if let convertImage = UIImagePNGRepresentation(image) {
convertImage.writeToURL(imageUrl, atomically: true)
}
}
}
}
}catch {
print(error)
}
self.dismissViewControllerAnimated(true, completion: nil)
}