How can I convert from UIImage to HEIF / HEIC Data in Swift?
No. Never use NSKeyedArchiver to convert your image to Data. Choose an image format (HEIC, PNG, JPEG, etc) and get its data representation. You should only use PNG when saving images to use in your UI. Most of the time jpeg is the preferred choice. If the device supports HEIC it is an option considering the image quality and reduced data size.
If you need to check if the user device supports HEIC type you can do it as follow:
var isHeicSupported: Bool {
(CGImageDestinationCopyTypeIdentifiers() as! [String]).contains("public.heic")
}
If you need to convert your image to HEIC you need to get a CGImage
from your UIImage
and convert your UIImage
's imageOrientation
to CGImagePropertyOrientation
to preserve the orientation when creating its data representation:
extension UIImage {
var heic: Data? { heic() }
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, kCGImagePropertyOrientation: cgImageOrientation.rawValue] as CFDictionary)
guard CGImageDestinationFinalize(destination) else { return nil }
return mutableData as Data
}
}
extension CGImagePropertyOrientation {
init(_ uiOrientation: UIImage.Orientation) {
switch uiOrientation {
case .up: self = .up
case .upMirrored: self = .upMirrored
case .down: self = .down
case .downMirrored: self = .downMirrored
case .left: self = .left
case .leftMirrored: self = .leftMirrored
case .right: self = .right
case .rightMirrored: self = .rightMirrored
@unknown default:
fatalError()
}
}
}
extension UIImage {
var cgImageOrientation: CGImagePropertyOrientation { .init(imageOrientation) }
}
Usage for lossless compression:
if isHeicSupported, let heicData = image.heic {
// write your heic image data to disk
}
or adding compression to your image:
if isHeicSupported, let heicData = image.heic(compressionQuality: 0.75) {
// write your compressed heic image data to disk
}
Is HEIC/HEIF Supported By UIImage
While Zhao's answer works, it is fairly slow. The below is about 10-20 times faster. It still doesn't work in the simulator for some reason though so keep that in mind.
func convert(url: URL) -> UIImage? {
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
guard let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) else { return nil }
return UIImage(cgImage: cgImage)
}
This is kind of outlined on page 141 from the slides of a WWDC session but wasn't super clear to me before: https://devstreaming-cdn.apple.com/videos/wwdc/2017/511tj33587vdhds/511/511_working_with_heif_and_hevc.pdf
Unfortunately I still haven't been able to figure out a way to use images in the xcassets folder so you'll either have to include the files outside of assets or pull from on the web. If anyone knows a way around this please post.
Converting an Image from Heic to Jpeg/Jpg
There's a workaround to convert HEIC photos to JPEG before uploading them to the server :
NSData *jpgImageData = UIImageJPEGRepresentation(image, 0.7);
If you use PHAsset, the, in order to have the image object, you'll need to call this method from PHImageManager:
- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;
On server side you also have the ability to use this API or this website directly
Convert HEIF photo to JPEG for uploading to backend
(new solution, the previous one didn't keep the EXIF information)
To get the image as a JPEG photo, with EXIF information, create a CIImage object from the HEIF image data and use CIContext.jpegRepresentation(of: to get the jpeg-encoded image as a Data object
let imageManager = PHImageManager.default()
var photo : PHAsset
var options : PHImageRequestOptions
imageManager.requestImageData(for: photo, options: options, resultHandler: {
imageData,dataUTI,orientation,info in
let ciImage = CIImage(data: imageData!)
if #available(iOS 10.0, *) {
data = CIContext().jpegRepresentation(of: ciImage!, colorSpace: CGColorSpaceCreateDeviceRGB())!
// upload image data
}
Angular 9: How to convert HEIF file format to a known web format when uploading images
The solution for us, was to install heic2any:
npm install heic2any
Then import it in the component where it is needed:
import heic2any from "heic2any";
In the accept
attribute add the mimetype and the file extension.
image/jpeg, image/png, image/heif, .heic, .heif
And if you need to support upload of any kind of image:
image/*, .heic, .heif
We are using ngfSelect, and it looks like this (simplified):
<div ngfSelect
[accept] = "'image/*, .heic, .heif'"
(filesChange) = "imageSelected($event)">
Below is a simplified version of the imageSelected() function
public imageSelected(event) {
let f:File;
//RECEIVE IMAGE
if (event[0] && (event[0]['lastModified'] || event[0]['lastModifiedDate'])) {
f = event[0];
if (event.length > 1) {
event.splice(0,event.length-1);
f = event[0];
}
} else if (event.target && event.target.files && event.target.files[0]) {
f = event.target.files[0];
}
if (!f) {
//Handle error and exit
}
let blob:Blob = f;
let file:File = f;
let convProm:Promise<any>;
//CONVERT HEIC TO JPG
if (/image\/hei(c|f)/.test(f.type)) {
convProm = heic2any({blob,toType:"image/jpeg",quality:0}).then((jpgBlob:Blob) => {
//Change the name of the file according to the new format
let newName = f.name.replace(/\.[^/.]+$/, ".jpg");
//Convert blob back to file
file = this.blobToFile(jpgBlob,newName);
}).catch(err => {
//Handle error
});
} else {
//This is not a HEIC image so we can just resolve
convProm = Promise.resolve(true);
}
//ADD IMAGE FILE TO CROPPERJS EDITOR
convProm.then(() => {
let reader = new FileReader();
let _thisComp = this;
//Add file to FileReader
if (file) {
reader.readAsDataURL(file);
}
//Listen for FileReader to get ready
reader.onload = function () {
//Set imageUrl. This will trigger initialization of cropper via imageLoaded()
//as configured in the html img tag:
//<img #image id="image" [src]="imageUrl" (load)="imageLoaded($event)" class="cropper">
_thisComp.imageUrl = reader.result;
}
});
}
private blobToFile = (theBlob: Blob, fileName:string): File => {
let b: any = theBlob;
//A Blob() is almost a File() - it's just missing the two properties below which we will add
b.lastModified = new Date();
b.name = fileName;
//Cast to a File() type
return <File>theBlob;
}
The above may not be the prettiest solution, but I hope it can be of help and inspiration to others facing the same challenge.
Related Topics
Uitextview Linespacing Causes Different Cursor Height Inbetween Paragraph Lines
Setting Up a Parent-Child Relationship in Core Data
Is It Ok to Place Most Logic and Models in the Appdelegate
Tracking Mkmapview Centercoordinate While Panning
Not Getting Email and Public Profile Using Facebook 4.4.0 Sdk
Decrypt Value from Blowfish in Objective-C Code
Xcode Warning When Using Mapkit and Corelocation
Ui Testing Failure - Neither Element Nor Any Descendant Has Keyboard Focus on Securetextfield
The Sandbox Is Not in Sync with the Podfile.Lock-Ios
How to Stop Symbolicate Adding "<Redacted>" Pieces to iOS Crash Log
Simple Low-Latency Audio Playback in iOS Swift
Intercepting Phone Call - iPhone (Correct Method to Hook in Coretelephony)
Uiimagejpegrepresentation Received Memory Warning
Swift JSON Error:Could Not Cast Value of Type '_Nsdictionarym' to 'Nsarray'
How to Get Index Path of Cell on Switch Change Event in Section Based Table View