Falco
Jul 13, 2021 • 6 min read
数週間前より、Discourseへの画像アップロードは、アップロード前にクライアントサイドで画像を圧縮・最適化する技術のおかげで、より高速かつより軽量になりました。このブログ記事では、この機能がどのように動作するか、そしてDiscourseにどのように実装したかについて説明します。
はじめに
Discourse、そして一般的なフォーラムは主に¶段落のテキストを扱うものですが、インターネット上のdiscourse(言論) はメディアで構成されることが増えています。画像はユーザー投稿の主要な要素となっています。また、Discourseを開始してから8年の間に、スマートフォンのカメラが広く普及し、瞬時に素晴らしい写真を撮影できるようになったことも言及する価値があります。
これらの理由から、プロジェクトのごく早い段階で、アップロード前の画像最適化に関する機能リクエストを受け取りました。これは、不安定なモバイル回線からファイルをアップロードする時間を意識しながら、ユーザーが簡単に画像を共有できるようにしたいと考えるスタッフによって動機づけられています。
クライアントサイドの画像圧縮における主要な実験の一つが、Google Chrome LabsのチームによるSquoosh.appです。このトークで初めて発表され、WebAssemblyのような新しいブラウザ機能を活用することで、クライアントサイドで高品質な画像圧縮を実現できるという確かな証明となりました。
新しい画像最適化パイプライン
Discourseで投稿を作成中にアップロードボタンをクリックすると、OSのファイルピッカーが開き、投稿に追加するファイルを1つ以上選択できます。画像が検出されると、新しい機能が作動し、サーバーへのファイルアップロード直前にジャストインタイムで最適化を実行します。
新しいフローの概要は以下のとおりです:
主に3つのステップに分けられます:
- デコード
- リサイズ
- エンコード
画像のデコード
有効な画像であることを確認した後、次のステップはこのFileオブジェクトを赤、緑、青、アルファの単純なバイト配列に変換することです。最初のステップは、JPEGやPNGのファイルをデコードすることですが、幸いなことにすべてのモダンブラウザには、これをまさに行う非常にシンプルなメソッドがあります:self.createImageBitmap()です。これは素晴らしいブラウザAPIで、メインスレッドをオフにしてこの処理を行うため、一般的なデバイスで大きな画像をデコードする際もUIが遅くなりません。このメソッドはSafariでは利用できないため、Safariではasync属性を持つimgタグを使用してファイルをデコードする必要があります。
画像をデコードした後、結果をCanvasに描画して最終的にRGBA配列を取得する必要があります。ワーカー内でOffscreenCanvasを使用する方法も検討しましたが、これは広くは利用できません。(内部テスト中に、iOS 15 BetaでユーザーがCanvasに完全な黒画像を報告しました。つまり、CanvasRenderingContext2D.getImageDataがすべての値が0の配列を返すことがありました。これに対する保護を追加しましたが、Appleが今秋のiOS 15一般リリース前にこの問題を修正することを願っています。)
(また、この機能の初期実装では、デコードタスクはWebAssemblyを使用して行われていましたが、ブラウザのネイティブAPIを使用する方がはるかに優れています。複数の画像タイプをサポートでき、既存のAPIで十分だからです。)
画像のリサイズ
スマートフォンのカメラは常に向上しており、その結果、画像のサイズは増え続けています。例えば、Galaxy S21 Ultraは12000x9000ピクセルの写真を撮影できます。そのサイズの画像は多くの用途に役立ちますが、利用可能なコンテンツ幅が700ピクセルを超えることがほとんどない典型的なDiscourseの投稿本文には巨大すぎます。これが画像最適化パイプラインにおける最初の戦略につながりました:画像を小さなサイズにリサイズすることです。
ここでサイト管理者に公開する主な設定項目が2つあります:リサイズをトリガーする画像の最小幅と、リサイズされる画像のターゲットとなるリサイズ幅です。両設定のデフォルト値は1920pxで、これにより現代のほとんどのスマートフォンの写真やデスクトップでのフルスクリーンのスクリーンショットをリサイズすることになります。
実際のリサイズ操作の実行については、Squooshが使用している同じライブラリを使用することにしました:
この小さなRustライブラリは高速なリサイズを実装しており、Lanczos3メソッドを使用したダウンスケールが可能です。Squooshアプリでは、wasm-bindgenを使用してリサイズをパッケージ化し、ブラウザのコンテキストで実行できるようにしています。残念ながら、彼らはwasm-bindgenのターゲットとしてwebを使用しており、これは任意のモダンブラウザにインポートできるネイティブESモジュールを生成します。これは私たちの計画に少し支障をきたしました。なぜなら、リサイズ操作をバックグラウンドの専用ワーカーで実行するのですが、Firefoxはまだワーカーをtype: 'module'として実行することをサポートしていないからです。この状況を回避するために、このライブラリのSquooshパッケージをフォークし、wasm-bindgenのターゲットをno-modulesに変更する必要がありました。結果として少し扱いにくいAPIになりますが、互換性が大幅に向上します。
画像のエンコード
2014年に遡り、Mozillaは新しいプロジェクトを発表しました:MozJPEGです。これは数十年前のJPEGファイル形式の改良されたエンコーダーであり、ブラウザの既存のデコーダーとの互換性を壊しません。
このライブラリもSquooshアプリではWebAssemblyとしてパッケージ化されており、ここではEmscriptenを使用しています。そしてここでも、元のパッケージがESモジュールを使用しており、デフォルトの厳格なコンテンツセキュリティポリシー(CSP)と互換性がなかったため、フォークする必要がありました。
画像エンコードについて、フォーラム管理者にはもう一つの設定項目があります:エンコーダーの品質パラメーターです。アップストリームのデフォルトである75に従っていますが、必要に応じて調整できます:
qualityスイッチを使用すると、圧縮ファイルサイズと再構築された画像の品質をトレードオフできます。品質設定が高いほど、JPEGファイルは大きくなり、出力画像は元の入力に近くなります。通常は、元の画像と視覚的に区別がつかないものに解凍される最低品質設定(最小ファイル)を使用したいものです。この目的のために、写真画像の品質設定は一般的に50から95の間(デフォルトは75)であるべきです。-quality 75で欠陥が見られる場合は、出力画像に満足するまで5または10カウントずつ上げてください。(最適な設定は画像によって異なります。)
結果
画像サンプル
こちらはPixel 3A XLで撮影した写真で、Discourseサイトでデフォルト最適化を行う前後の比較です:
元のファイルは4032x3024で3.7MBですが、圧縮後は1920x1440で416KBになります。
集計統計
いくつかのDiscourseインスタンスでこの機能を有効にした後、さまざまなカテゴリのサイトにわたって結果のファイルサイズを測定することができました。
以下は、2021-06-29にこの機能が有効になったyo-yo Discourseサイトの結果です:
そして、同じく2021-06-29に機能が有効になったガーデニング愛好家のサイトの結果です:
極端な例
Discourseはデフォルトでユーザーのアップロードを4MB未満のファイルに制限しています。これにはいくつかの理由があり、その一つはストレージコストが特定のタイプのコミュニティの管理者にとって主な悩みの種になり得るからです。この新機能により、ユーザーはその制限をはるかに超えるファイルを直接アップロードでき、自動的にリサイズされて既存の制限に収まるようになります。これにより、小規模なコミュニティに負担をかけることなく、ユーザーのシームレスなインタラクションが実現します。
典型的なデスクトップでは、50MBを超えるJPEGやPNGがDiscourseで最適化されアップロードされたという報告があります。
Metaのテストトピックでこの機能をぜひ試してみてください。
データ劣化についての注記
この機能には多くのメリットがありますが、主なデメリットであるデータ劣化についての説明も避けるべきではありません。
この機能において、過度なデータ劣化に対するいくつかの保護があります:
トリガーする最小画像サイズ:設定可能なしきい値よりもファイルサイズが小さい画像には変更を加えません
リサイズする最小画像幅:設定可能なしきい値よりも幅が小さい画像はダウンスケールしません
そして、この機能はサイト管理者が完全に無効にできることを覚えておくことが非常に重要です。コミュニティが高品質な画像共有を中心に展開している場合は、ワンクリックでこのクライアントサイド最適化機能全体を無効にできます。
まとめ
Discourseは、フリーかつオープンソースのプロジェクトとして、「私たちのスタック」を形成するすべてのソフトウェアなしには存在できません。この機能の構築に使用されたすべてのソフトウェアに感謝します:
- SquooshおよびGoogle Chrome Labsチーム
- Pistonオープンソースゲームエンジンのメンテナー
- MozillaおよびMozJPEGのメンテナー
- Emscripten
- RustおよびWebAssemblyワーキンググループ
原文はこちら:
Good Loopでは、Discourseのセルフホスティングを安価で提供しています。開発元であるCDCK社の協力のもと、公式ブログ記事の翻訳・公開など、日本での普及にも努めています。






