I am mantaining a webapp (PWA) written in Angular 9, where users upload images and crop, rotate etc. in cropperjs.
On iOS a new image format (HEIF) is becoming the standard and these users are complaining that they are not able to upload their photos. It seems that sometimes iOS will convert to jpg automatically, and sometimes it doesn't. Therefore we need to be able to receive images in HEIF format.
I tried adding the mimetype image/heif to the accept
attribute but the *.heic images are still dimmed on iOS. I seems that many are just choosing to accept all files, but that is not an option for this web app.
Also cropperjs does not support the HEIF image format, so how do we convert to a know web format?
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.