Generate a thumbnail from UIImage in Swift - 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)

Related

How can I convert a PHLivePhoto to UIImage?

In my app I have a section of code where I need to account for a PHLivePhoto type object and convert this to a UIImage. I believe it has to do with a PHAsset, PHAssetResource, or PHImageManager but unclear how to perform the conversion. What's a good approach on how to convert from PHLivePhoto -> UIImage? Thanks for the help!
if let livePhoto = object as? PHLivePhoto {
let livePhotoResources = PHAssetResource.assetResources(for: livePhoto)
print("\(livePhotoResources)")
// imageBucket is of type UIImage[]
// livePhoto is of type PHLivePhoto but I don't know how to convert this to type PHAsset
viewModel.imageBucket.append(convertImageAsset(asset: **WHAT_DO_I_INSERT_HERE**))
}
...
func convertImageAsset(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var tmpImage = UIImage()
option.isSynchronous = true
manager.requestImage(
for: asset,
targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight),
contentMode: .aspectFit,
options: option,
resultHandler: {(result, info)->Void in
tmpImage = result!
})
return tmpImage
}
results in:
[<PHAssetResource: 0x28225cdc0> {
type: photo
uti: public.heic
filename: IMG_5442.heic
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.HEIC
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}, <PHAssetResource: 0x28226bc00> {
type: video_cmpl
uti: com.apple.quicktime-movie
filename: IMG_5442.mov
asset: (null)
locallyAvailable: YES
fileURL: file:///private/var/mobile/Containers/Data/Application/2FB56305-7600-4A8E-9C67-A71B4A4A9607/tmp/live-photo-bundle/75FD3D97-F13E-4F79-A6E9-F0743D443FDD.pvt/IMG_5442.MOV
width: 0
height: 0
fileSize: 0
analysisType: unavailable
cplResourceType: Unknown
isCurrent: NO
isInCloud: NO
}]
You need to use PHAsset to fetch the asset, then request image data from PHImageManager.default()
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
guard let assetIdentifier = results.first?.assetIdentifier else {
return
}
if let phAsset = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil).firstObject {
PHImageManager.default().requestImageDataAndOrientation(for: phAsset, options: nil) { [weak self] data, _, _, _ in
if let data = data, let image = UIImage(data: data) {
self?.viewModel.imageBucket.append(image)
}
}
}
}
To get assetIdentifier, you need to create PHPickerConfiguration object using the shared photo library. Creating a configuration without a photo library provides only asset data, and doesn't include asset identifiers.
var configuration = PHPickerConfiguration(photoLibrary: .shared())
// Set the filter type according to the user’s selection. .images is a filter to display images, including Live Photos.
configuration.filter = .images
// Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
configuration.preferredAssetRepresentationMode = .current
// Set the selection limit.
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
The problem with the accepted answer is that fetching a PHAsset will require photo library access, and one of the main advantages of PHPickerViewController is being able to get the photos without asking for permissions, and completely avoiding all the related edge cases.
So another way of getting a live photo's image would be:
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
for result in results {
// Live photos
if result.itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
result.itemProvider.loadObject(ofClass: PHLivePhoto.self, completionHandler: { livePhoto, error in
let resources = PHAssetResource.assetResources(for: livePhoto as! PHLivePhoto)
let photo = resources.first(where: { $0.type == .photo })!
let imageData = NSMutableData()
PHAssetResourceManager.default().requestData(for: photo, options: nil, dataReceivedHandler: { data in
imageData.append(data)
}, completionHandler: { error in
_ = UIImage(data: imageData as Data)!
})
})
}
}
}

How to Create thumbnail from local files

I want to create a thumbnail image for files (word, excel, video ....)
This what i did:
import QuickLook
class ThumbsCreator: NSObject {
private var file : File?
init(file: File?) {
super.init()
self.file = file
}
func createThumb() {
let url = URL(string: (self.file?.path()))
}
}
After a lot of search, I found this solution :
import PDFKit
import AVKit
import WebKit
func createThumb() {
let url = URL(string: (self.file?.path()))
switch file?.type {
case: FileType.image.rawValue:
let image = UIImage(contentsOfFile: (url?.path)!)
_finalImage = self.createScaledImage(image: image!)
break
case: FileType.office.rawValue:
//Loading.......
break
case FileType.Pdf.rawValue:
guard let doc = PDFDocument(url: url!) else {return}
guard let page = doc.page(at: 0) else {return}
_finalImage = page.thumbnail(of: CGSize(width: 768, height: 1024), for: .cropBox)
break
case: FileType.video.rawValue:
let asset = AVAsset(url: url!)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTime(seconds: 2, preferredTimescale: 1)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
_finalImage = UIImage(cgImage: imageRef)
} catch let error{
print("Error: \(error)")
}
break
}
}
func createScaledImage(image: UIImage) {
let THUMB_WIDTH = 150.0 - 40.0
let THUMB_HEIGHT = THUMB_WIDTH - 23.0
var itemThumb = resizeImage(image: image, constraintSize: CGSize(width: THUMB_WIDTH, height: THUMB_HEIGHT))
let thumbRect = CGRect(x: 0, y: 0, width: 10, height: 10)
UIGraphicsBeginImageContextWithOptions(thumbRect.size, true, 0.0)
let context = UIGraphicsGetCurrentContext()
// Fill a white rect
context?.setFillColor(gray: 1.0, alpha: 1.0)
context?.fill(thumbRect)
// Stroke a gray rect
let comps : [CGFloat] = [0.8, 0.8, 0.8, 1]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let strokeColor = CGColor(colorSpace: colorSpace, components: comps)
context?.setStrokeColor(strokeColor!)
UIRectFrame(thumbRect)
//CGColorRelease(strokeColor!)
itemThumb.draw(in: thumbRect.insetBy(dx: 1, dy: 1))
itemThumb = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
self.finishThumCreation(image: image)
}
}
Starting from iOS 13 and macOS 10.15, there is the QuickLook Thumbnailing API. It supports any file format for which the OS can provide a preview: either because the OS knows this format or because the owner of the third-party format provided a QuickLook plugin.
Here is an example based on Apple's tutorial:
func thumbnail(for fileURL: URL, size: CGSize, scale: CGFloat) {
let request = QLThumbnailGenerator
.Request(fileAt: fileURL, size: size, scale: scale,
representationTypes: .lowQualityThumbnail)
QLThumbnailGenerator.shared.generateRepresentations(for: request)
{ (thumbnail, type, error) in
DispatchQueue.main.async {
if thumbnail == nil || error != nil {
// Handle the error case gracefully.
} else {
// Display the thumbnail that you created.
}
}
}
}
On macOS before 10.15, in my app I fallback to NSWorkspace.shared.icon(forFile:) which provides a document icon based on the file type (but not a thumbnail).
You can use https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller/1616801-icons
var icons: [UIImage] { get }
let controller = UIDocumentInteractionController(url:someUrl)
print(controller.icons.first)
Only for a video
extension UIViewController {
func thumbnail(_ sourceURL:URL) -> UIImage {
let asset = AVAsset(url: sourceURL)
let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true
let time = CMTime(seconds: 1, preferredTimescale: 1)
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: imageRef)
} catch {
print(error)
return UIImage(named: "NoVideos")!
}
}
}
There's no good API for this yet. There is NSURLThumbnailDictionaryKey, but YMMV. You can indeed get icons via UIDocumentInteractionController.

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.

How do you use a button click function to convert an PHAsset to a UIImage

Ok, so i've been at this for a while and I just can't seem to solve it.
I have an ImagePicker which bundles images into PHAssest. It's from https://github.com/hyperoslo/ImagePicker
When an image is selected, the done button is activated. I have a UIImageView which when i press the func doneButtonDidPress(images: [UIImage]), would convert the PHAsset into a UIImage and load it into the ImageView. Currently, this is my code which doesn't work:
// CONVERT PHASSET TO UIIMAGE
func getAsset(asset: PHAsset) -> UIImage {
let manager = PHImageManager.defaultManager()
let option = PHImageRequestOptions()
var image = UIImage()
option.synchronous = true
manager.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFit, options: option, resultHandler: {(result, info)->Void in
image = result!
})
return image
}
func doneButtonDidPress(images: [UIImage]) {
self.imagePickerController.dismissViewControllerAnimated(true, completion: nil)
var selectedImg = imagePickerController.stack.assets
getAsset(selectedImg)
self.SelectedImageView.image = UIImage(named: selectedImg)
}
Which flags with errors. Can anyone help me here?! Thankyou
I am getting UIImage from PHAsset like :
func getAsset(asset: PHAsset) -> UIImage {
var image = UIImage()
let imgManager = PHImageManager.defaultManager()
let requestOptions = PHImageRequestOptions()
requestOptions.synchronous = true
imgManager.requestImageForAsset(asset, targetSize: CGSizeMake(300.0, 300.0), contentMode: PHImageContentMode.AspectFit, options: requestOptions, resultHandler: { (img, _) in
image = img
})
return image
}
This code is tested & currently working in my application.
Done Button method should be like :
func doneButtonDidPress(image: [UIImage]) {
self.imagePickerController.dismissViewControllerAnimated(true, completion: nil)
var selectedImg = imagePickerController.stack.assets.first
self.selectedImageView.image = getAsset(selectedImg)
}