3

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?

Jette
  • 2,459
  • 28
  • 37

2 Answers2

6

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.

Jette
  • 2,459
  • 28
  • 37
0

heic2any works at ONLY client side; which means that if you use heic2any; you will possibly break angular build - pre-render and you will face with refresh route problem.

  • For now it works fine, but I am aware that heic2any can potentially cause trouble for us in the future. I have been searching for an Angular-friendly plugin that does the same, but unfortunately did not find one. – Jette May 03 '21 at 09:03
  • Unfourtunately , HEIC format is really headache and some of browser do not support it fully. Therefore; packages can not provide perfect solution for it at the moment – Fatih Kurnaz May 13 '21 at 21:37