12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NIJIBOXAdvent Calendar 2019

Day 25

Firebase Storageで画像回転(Exif)に対応する。(クライアント側とFirebase側の2通り)

Last updated at Posted at 2019-12-24

この記事について

画像をアップロードして、表示するものをつくったところ、「あ、iPhoneでアップしたら横向きになっちゃった」っていうのは割とアルアルだと思います。対応方法としては、

  • クライアント側(JS)で回転処理してからアップロードする
  • バックエンド側で回転処理をして保存する

の2パターンかと思いますが、Firebase,Nuxt(+Vuetify),TypeScriptだったときの、両方についての対応サンプルとなります。

クライアント側対応版

blueimp-load-image を使いました。こちら

pages/index.vue(template部分から抜粋)
<v-img
  :src="previewImageSrc"
  aspect-ratio="1"
  class="previewImg"
></v-img>
<v-file-input
  @change="onChangeFileInput"
  label="画像の変更"
></v-file-input>
pages/index.vue(script部分から抜粋)
onChangeFileInput(file: any) {
    if (file) {
      BlueimpLoader.parseMetaData(file, (data: any) => {
        const options = {
          maxHeight: 500,
          maxWidth: 500,
          canvas: true,
          orientation: 1
        }
        if (data.exif) {
          options.orientation = data.exif.get('Orientation')
        }
        BlueimpLoader(
          file,
          (canvas: HTMLCanvasElement) => {
            this.previewImageSrc = canvas.toDataURL(file.type)
            canvas.toBlob((blob) => {
              this.imageBlob = blob
            })
          },
          options
        )
      })
    } else {
      this.previewImageSrc = ''
      this.imageBlob = null
    }
  }

全コード
これでプレビュー表示も回転しますし、this.imageBlob をFirebase StorageにアップすればOKだね。という話になります。

Firebase側対応版

Firebase Functions を使います。Storageに画像がアップロードされたら起動する感じにしておきます。

基本的には、Firebase公式ドキュメントにサムネを生成するコードがのっているので、ほぼほぼそれを参考にしました。

functions/src/index.ts(抜粋)
await spawn('convert', [
    tempDownloadFilePath,
    '-auto-orient',
    '-thumbnail',
    '600x600>',
    tempConvertedFilePath
  ])

全コード

やる前はメタデータのExifでOrientationを取得して。。。みたいなのを想定していたのですが、
まさかのパラメータに-auto-orient追加だけで、できてしまいました。感動!!!

注意すべきは

exports.convertImage = functions.storage.object().onFinalize(async (object) => {

onFinalize のイベントで実行されるので、変換した画像をStorageに保存するときに、またこのスクリプトが実行されます。なので、下手したら無限ループするやんってことでしょうか(公式サンプルも今回のもファイル名判定してループを止めています)。

なもんで、実際に導入するときはemulatorとかしっかりつかって検証した上でやりましょう。

また、Databaseに変換後のパスを保存したりとか、このFunctionsの変換処理が終わったタイミングってどうやって判断すんだっけとか、色々考えないと、実際のサービスなどでは使えないでしょうね。

最後に

どっちがいいかはその時々で違うと思います。クライアント側でやるほうが楽だし、アップロードの通信量を削減できるというメリットもあると思います。

でもバックエンド側でやれば、ゆーてもバリデーション的な位置づけで、変な画像は許可させないぞ、という立ち位置もとれるというのはGoodかなぁと。

ではでは、みなさまメリクリ〜

12
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?