Jette
Jette

Reputation: 2609

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

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?

Upvotes: 3

Views: 6831

Answers (3)

In first answer conversion from blob to file is not correct.

My version:

async uploadFile(file: File) {
    this.displayPreloader = true;
    const convertedFile = await this.convertIfHeic(file);
    //file uploading
}
    
async convertIfHeic(file: File): Promise<File> {
    console.log('c if heic', file)
    if (/\.hei(c|f)+$/.test(file.name.toLowerCase())) {
        console.log('convert heic...')
        const blob = await heic2any({blob: file, toType: "image/jpeg", quality: 0}) as Blob;
        const fileName = file.name.replace(/\.[^/.]+$/, ".jpg")
        return this.blobToFile(blob, fileName)
    }
    return file;
}

private blobToFile = (theBlob: Blob, fileName:string): File => {
    const options: FilePropertyBag = {type: theBlob.type};
    return new File([theBlob], fileName, options);
}

Upvotes: 0

Fatih Kurnaz
Fatih Kurnaz

Reputation: 31

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.

Upvotes: 0

Jette
Jette

Reputation: 2609

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.

Upvotes: 6

Related Questions