reactjsnext.jsjpegimage-conversionheic

How to convert HEIC to JPG in React?


HEIC is Apple's own format to store high resolution images made with iOS cameras. But I would store on backend JPG, because HEIC is not displayed in most browsers, not even in Safari.

SOLUTION 1

I tried this for the conversion:

const buffer = Buffer.from(await file.arrayBuffer())
const d = heicConvert({ buffer, format: 'JPEG' })

const imgBase64 = btoa(
    d.reduce((data, byte) => `${data}${String.fromCharCode(byte)}`, '')
)

but because I use Next.js it is not compatible with it.

Failed to compile.

./node_modules/libheif-js/libheif/libheif.js
Module not found: Can't resolve 'fs' in '/Users/janoskukoda/Workspace/tikex/portal/team/node_modules/libheif-js/libheif'

SOLUTION 2

I tried this also:

export default uploadImage

const buffer = await file.arrayBuffer()
const image = sharp(buffer)
const metadata = await image.metadata()

if (metadata.format === 'heic') {
    // Convert the image to JPG
    const jpgBuffer = await image.jpeg().toBuffer()

    // Encode the JPG image as a base64 string
    const imgBase64 = btoa(
        jpgBuffer.reduce((data, byte) => `${data}${String.fromCharCode(byte)}`, '')
    )
}

But I can not compile, seems sharp is not recommended to use in client side.

Do you have any other way to do it?

Anyway idea comes here: https://itnext.io/tackling-iphone-or-ipad-images-support-in-browser-8e3e64e9aaa1

If you show me a solution uses serverless api, it is also ok. It is important file comes from html input element.

SOLUTION 3

import loadImage from 'blueimp-load-image'

convertedImage = await new Promise((resolve, reject) => {
    loadImage(
        propsAndFile.file,
        resolve,
        { orientation: true, canvas: true },
        reject
    )
})

Solution

  • Before getting to the answer: You can never trust data uploaded by a client — you must always validate/convert using a process that is not accessible by a user (a backend process) to ensure data validity: even if there are mechanisms in place to validate received network requests as coming from an authenticated user on your site, any user can still use developer tools to execute arbitrary JavaScript and send whatever kinds of network requests with whatever payloads that they want to.

    Regarding iOS devices and getting JPEG instead of HEIF from a file input: You don't need to do this yourself — iOS can do it for you.

    <input type="file"> elements support an accept attribute that can be used to restrict the kinds of file media types that can be uploaded: read more at the Limiting accepted file types section of the MDN documentation article for <input type="file">.

    Below is an example which shows how to use that attribute. When an iOS user selects the input, they can choose to take a photo using their camera or select one from the photo library. iOS will perform the necessary file conversion to JPEG automatically in both of these cases (for example, even when a selected image from the photo library is in HEIF format). The example demonstrates this when you try it with an HEIF-encoded image on an iOS device:

    const input = document.getElementById('image-upload');
    const output = document.getElementById('output');
    
    const updateDisplayedFileInfo = () => {
      const file = input.files?.[0];
    
      if (!file) {
        output.textContent = 'No file selected';
        return;
      }
    
      const dateModified = new Date(file.lastModified).toISOString();
      const {name, size, type} = file;
    
      const data = {
        dateModified,
        name,
        size,
        type,
      };
    
      const json = JSON.stringify(data, null, 2);
      output.textContent = json;
    };
    
    input.addEventListener('change', updateDisplayedFileInfo);
    updateDisplayedFileInfo();
    pre { background-color: hsla(0, 0%, 50%, 0.1); padding: 0.5rem; } code { font-family: monospace; }
    <input id="image-upload" type="file" accept="image/jpeg" />
    <pre><code id="output"></code></pre>