[Fixed] Angular 9: How to convert HEIF file format to a known web format when uploading images

Issue

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?

Solution

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.

Leave a Reply

(*) Required, Your email will not be published