How to Convert from Uiimage to Heif/Heic Data in Swift

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



Leave a reply



Submit