CGImageSource to CGImage? - swift

I got this following code that access a PHAsset. I need to pass a CGImage out of this:
let imageManager = PHImageManager.defaultManager()
imageManager.requestImageDataForAsset(self.asset!, options: nil, resultHandler:{
(data, responseString, imageOriet, info) -> Void in
let imageData: NSData = data!
if let imageSource = CGImageSourceCreateWithData(imageData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
//How do I create a CGImage now?
}
})
I need to extract a CGImage out of this. Having trouble getting there...

Once you have the image source you can create an image using CGImageSourceCreateImageAtIndex:
let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)

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).")
}
}
}
}

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)

Uiview to Data (Saving a Gif to firebase)

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 !

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
}

EXIF data read and write

I searched for getting the EXIF data from picture files and write them back for Swift. But I only could find predefied libs for different languages.
I also found references to "CFDictionaryGetValue", but which keys do I need to get the data? And how can I write it back?
I'm using this to get EXIF infos from an image file:
import ImageIO
let fileURL = theURLToTheImageFile
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}
It gives you a dictionary containing various informations like the color profile - the EXIF info specifically is in dict["{Exif}"].
Swift 4
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {(bytes: UnsafePointer<UInt8>)->Void in
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count) {
let source = CGImageSourceCreateWithData(cfData, nil)
exifData = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
}
}
}
return exifData
}
}
Swift 5
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {
let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count),
let source = CGImageSourceCreateWithData(cfData, nil) {
exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
}
}
}
return exifData
}
}
You can use AVAssetExportSession to write metadata.
let asset = AVAsset(url: existingUrl)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
exportSession?.outputURL = newURL
exportSession?.metadata = [
// whatever [AVMetadataItem] you want to write
]
exportSession?.exportAsynchronously {
// respond to file writing completion
}