HEIC 格式的圖片怎麼辦？使用 heic2any.js 轉換為 JPEG 或 PNG
Tags: heic, javascript, tutorial, convert
Published: 2023-02-26 05:38:05

## 本篇要解決的問題

「本文介紹了如何使用 heic2any.js，一個 JavaScript 套件，可以將 HEIC 格式的圖片轉換為 JPEG 或 PNG 格式。同時提供了程式碼範例和使用方法，方便大家進行實際操作。」

上面那段是 ChatGPT 提供的頁面 description，因為看上去蠻有這麼一回事的，就拿來當本篇第一段重點。（是有沒有這麼懶）

簡單來說，就是前陣子 August 遇到了一個需求，要把 HEIC 的圖檔呈現在網頁上，也是因為遇到這個需求，才知道，咦？原來 HEIC 的圖片格式不能直接放在網頁上啊？（驚）

詢問了前同事後，知道了 heic2any.js 這個套件，然後進到套件的 GitHub 頁面後，咦？沒有寫使用說明啊？（驚 again）

然後，又因為懶，所以直接請 ChatGPT 寫一個 heic2any.js 的範例，結果，出來的程式碼是錯的！（驚 again * 2）

沒辦法，只好翻了一下套件的原始碼，再參考 ChatGPT 的寫法，去研究怎麼使用。

相信看到這篇的你，也是對官方沒有提供說明文件而感到震驚跟打擊，所以本篇除了會寫一個 Demo 出來，也會提供研究出來的程式碼。

大家取用本篇的程式碼前，希望可以分享本篇，或對 Demo 的 GitHub 專案打個星星。

畢竟這也是 August 花了時間整理出來的。

heic2any：<https://github.com/alexcorvi/heic2any>

本篇實作 Demo：<https://letswritetw.github.io/letswrite-heic2any/>

---

## 什麼是 HEIC？

以下是 ChatGPT 給的解釋：

> HEIC 是 High Efficiency Image Format 的縮寫，是一種現代的圖像格式，由國際標準組織 MPEG（Moving Picture Experts Group）所定義。HEIC 格式通常使用在 iOS 11 及以上版本的 iPhone、iPad 和 macOS High Sierra 及以上版本的 Mac 上，作為照片和圖像的預設格式。

> HEIC 格式相比傳統的 JPEG 格式具有更好的壓縮效率，可以在保持同樣圖像質量的情況下，大幅減小檔案大小。此外，HEIC 格式還支援更多的高級功能，例如多幅圖像的合成、深度圖和 Live Photo 等。

> 不過，HEIC 格式目前在一些應用上還存在一些限制，例如在某些瀏覽器和操作系統上無法直接顯示，需要進行轉換才能使用。因此，對於需要與多種平台和應用進行兼容性的使用者，可能需要將 HEIC 格式的圖片轉換為其他常見的圖像格式，例如 JPEG 或 PNG。

> HEIC 格式的圖片在 Windows 和 Android 系統上也需要進行轉換才能直接顯示。

簡單來說，就是 iPhone、Mac 宇宙產出來的無生命但卻讓工程師處理起來要多一道工的格式。But，HEIC 格式比 JPEG 格式確實有很多優點，只是目前還存在一些兼容性問題，所以也不能說這個產物是投錯胎了。

大部份情況會是後端在收到圖，要存在圖庫或轉為 Base64 存在資料庫前，會先轉為 PNG 或 JPG 來儲存，之後 API 返回的圖檔路徑或是 Base64 就不會再是 HEIC。

不過，人生就是人生，難免會有存進去前漏了轉檔的意外，就會需要由前端來處理。

---

## 使用 heic2any.js

安裝 heic2any.js 的方式就跟我們使用其它 JavaScript 套件一樣，可以用 CDN 直接引用，或是用 npm package install 後再用 import 使用。

這邊，建議用 CDN 的方式，因為 heic2any.js 的檔案很~~~~大，未壓縮的檔案大小是 2.43 MB，壓縮過的也有 1.15 MB。用 CDN 來處理才不會對自己的主機產生太大的流量。

**CDN**

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/heic2any/0.0.3/heic2any.min.js"></script>
```

**npm**

```shell
$ npm install heic2any
或
$ yarn add heic2any
```

```js
import heic2any from 'heic2any';
```

引用了 JS 後，使用 heic2any.js 的函式如下：

```js
// 讀取圖片檔案
const file = document.querySelector('input[type=file]').files[0];

// 轉換圖片格式為 JPEG
heic2any.convert({
    blob: file,
    toType: 'image/jpeg',
    quality: 0.9
}).then(function(blob) {
    // 使用轉換後的 Blob 物件
    const imgSrc = URL.createObjectURL(blob);
}).catch(function (error) {
  // 轉換失敗時的處理
});
```

toType：轉換成什麼格式，可以有 `image/jpeg`、`image/png`，是 png 的話，`quality` 的參數會無效。

quality：轉檔後品質，只在 `toType` 是 `image/jpeg` 時有效，值是 0 - 1。

---

## 狀況 1：使用者從 iPhone 上傳圖

使用者從 iPhone 選取圖片然後上傳，有機會遇到檔案是 HEIC 的格式。

這時，可以在前端傳給後端前先轉檔，或是後端收到圖片後再轉檔。

本篇用 heic2any.js 就是前端轉好再給後端的方式。

呈現的結果可以在 Demo 頁上看到，這邊不再說明，直接上程式碼：

**HTML**

```html
<input class="hidden" id="file" type="file" accept="image/heic"/>
```

**JS**

```js
const fileInput = document.getElementById('file');

fileInput.addEventListener('change', async (e) => {
    const type = document.getElementById('type').value;
    const file = e.target.files[0];
    const result = await heic2any({
      blob: file,
      toType: toType: 'image/jpeg',
      quality: 1
    });
    const uri = URL.createObjectURL(result);
    
    // 執行下載
    const filename = file.name.split('.heic')[0];
    const link = document.createElement('a');
    link.download = `檔名.${type}`;
    link.href = uri;
    link.click();
        
    // 清空 file input 的值
    fileInput.value = '';
});
```

---

## 狀況 2：API 回應的圖檔，是 HEIC 的 Base64 格式

第二種狀況，之前使用者所傳的圖檔就是 HEIC 的格式，而後端在轉成 Base64 儲存前未轉檔，所以之後 API 給的圖片值是 HEIC 的。

**JS**

```js
// imgBase64 就是 HEIC 的 Base64 值，因為太長，就只顯示開頭的部份
const imgBase64 = "data:image/heic;base64,AAAAGGZ0eXBoZ......";

// 用 fetch 將 Base64 轉成 blob
const base64ToBlob = await fetch(imgBase64).then(res => res.blob());
const defaultImg = await heic2any({
  blob: base64ToBlob,
  toType: 'image/jpeg',
  quality: 1
});

// 執行下載
const src = URL.createObjectURL(defaultImg);
const link = document.createElement('a');
link.download = `檔名.${type}`;
link.href = src;
link.click();
```

這邊有一個偷懶的寫法，就是把 HEIC 的 Base64 用 `fetch` 的方式轉成 blob 格式。

之所以說懶，是因為用 `fetch` 只需要寫一行。

一般常看到轉 Base64 的方法其實是 `atob()`：

```js
const imgBase64 = "data:image/heic;base64,AAAAGGZ0eXBoZ......";

// 用 atob 將 Base64 轉為 blob
const base64ToBlob = (base64) => {
  const binary = atob(base64.split(',')[1]);
  const mime = base64.split(',')[0].match(/:(.*?);/)[1];
  const len = binary.length;
  const buffer = new ArrayBuffer(len);
  const view = new Uint8Array(buffer);
  for (let i = 0; i < len; i++) {
    view[i] = binary.charCodeAt(i);
  }
  return new Blob([buffer], { type: mime });
};

const defaultImg = await heic2any({
  blob: base64ToBlob(imgBase64),
  toType: 'image/jpeg',
  quality: 1
});
```

---

## 本篇 Demo 及原始碼

本篇的程式碼有放上 GitHub 上，也用 GitHub Pages 產生了 Demo，請自行取用，但希望在取用前能分享本篇，或在 GitHub 上點個星星，你的一個小小動作對本站都是大大的鼓勵。

原始碼：<https://github.com/letswritetw/letswrite-heic2any>

Demo：<https://letswritetw.github.io/letswrite-heic2any/>