Misskey & Webテクノロジー最前線

BlurHashでプレースホルダに彩りを

本連載では分散型マイクロブログ用ソフトウェアMisskeyの開発に関する紹介と、関連するWeb技術について解説を行っています。

今回は画像の美しいプレースホルダを描画することのできるBlurHashについて紹介します。

読み込み中画像の扱い

Webページやアプリでは画像が多用されますが、ユーザーの回線速度によっては表示されるまでは時間がかかります。

Misskeyにおいても、タイムラインではユーザーのアイコン、投稿に添付された画像のサムネイルなどが一画面に多く表示されます。

通常、画像が読み込まれるまでの間は空白の表示になることが多いですが、そのままだとレイアウトシフトを引き起こしたり、殺風景な印象を与えます。

図1 読み込み中画像を単にグレーで塗りつぶした例

そこで、表示に一工夫加えることで画面読み込み時のUXを向上させることができます。

BlurHash

Misskeyでは、読み込み時のUXを向上させるために、BlurHashというオープンソースのライブラリを使用しています。

BlurHashは、非常に小さい追加のデータ量で、画像が読み込まれるまでの間の簡易な「プレースホルダ」をレンダリングできるライブラリです。

プレースホルダとしてレンダリングされる画像は、元の画像を大きくぼかした(blur)もので、元画像の雰囲気を残しながら美しく「読み込み中」の表現ができます。BlurHashの公式サイト上で実際に試せますので、画像がどのようにプレースホルダに変換されるのか確認してみてください。

ちなみに開発しているのはフードデリバリーサービスのWoltです。

図2 読み込み中画像にBlurHashをプレースホルダとして使った例

仕組み

BlurHashの仕組みを見ておきましょう。

BlurHashでは、まずあらかじめ元画像データをプレースホルダのレンダリング用文字列(BlurHash string)にエンコードを行っておきます。

次のようなBase83の文字列としてエンコードされます。

eVMb9Zoi-sg2xe?dt8V[s:WA$oR}WBWAS0$:WUSaaybVt7t7oMj^R*

次に、ブラウザやアプリケーションで元画像(のURL)とBlurHash stringを受け取り、元画像を読み込んでいる間、BlurHash stringからプレースホルダ画像をレンダリングします。

BlurHash stringは画像のようなバイナリデータとは異なり、単なるASCII文字列なので、そのままHTMLに埋め込んだり、APIのJSONレスポンス内に埋め込めるのがポイントです。

図3 MisskeyのAPIレスポンスにはBlurHash stringが含まれている

画像を文字列に変換するアルゴリズムには、JPEGなどでも使われるDCT(離散コサイン変換)が採用されています。これによって、プレースホルダのデータ量を削減することが可能となっています。

実装

それでは、BlurHashの実装を解説します。

エンコード

Node.jsでのサーバー側エンコード処理の例を示します。

ノート: BlurHashの実装はJavaScript以外にも様々な言語でされています

エンコード時は画像データをUint8ClampedArrayで渡す必要があるので、sharpを使用して画像を読み込んでいます。

import * as blurhash from 'blurhash';
import sharp from 'sharp';

function encodeBlurhash(imageFilePath: string): Promise<string> {
	return new Promise((resolve, reject) => {
		sharp(imageFilePath)
			.raw()
			.ensureAlpha()
			.resize(64, 64, { fit: 'inside' })
			.toBuffer((err, buffer, info) => {
				if (err) return reject(err);

				let hash;

				try {
					hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
				} catch (e) {
					return reject(e);
				}

				resolve(hash);
			});
	});
}

エンコードして生成したBlurHash stringは、元ファイルと一緒にデータベースなどに保存しておきます。

デコード

ブラウザ側でのデコード処理の例を示します。

ノート: デコードについても様々な言語で実装されているので、WebだけでなくiOSやAndroidなどのネイティブアプリでも使うことができます。

import * as blurhash from 'blurhash';

function renderBlurhash(hash: string, canvas: HTMLCanvasElement): void {
	const buffer = blurhash.decode(hash, 32, 32);
	const ctx = canvas.getContext('2d');
	const imageData = ctx.createImageData(32, 32);
	imageData.data.set(buffer);
	ctx.putImageData(imageData, 0, 0);
}

なおMisskeyではパフォーマンスを追求するため、独自のデコーダー実装を行っています。

使う際のコツ

BlurHashを使う際のコツをまとめます。

サムネイルをエンコードする

画像を扱う際、元の画像とは別に、縮小されたサムネイルを用意することも一般的です。

BlurHashのエンコードは元画像ではなくサムネイルに対して行うようにするとパフォーマンスを向上できます。

BlurHashは大きくぼかした画像を出力するので、エンコード元画像の解像度は品質に影響を与えません。

高解像度でレンダリングしない

デコード時、Webの場合小さめのcanvasに描画してから、CSSでcanvas要素自体を所望のサイズに引き伸ばすとパフォーマンスを向上できます。

プレースホルダは大きくぼかした画像なので、高解像度でレンダリングしても無駄に処理量が増えるだけです。

詳細度を上げすぎない

BlurHashではエンコード時に、どれだけ詳細度(=ぼかしの弱さ)を上げるかをcomponentパラメータで1〜9の範囲で設定できます。

一見すると詳細度を上げたほうがより綺麗なプレースホルダを表示できそうですが、現在のBlurHashが採用しているDCTでは、むしろ醜くなる傾向があります。

また詳細度を上げるほどデータ量は増えますので、詳細度は上げすぎないほうが無難です。

図4 詳細度の比較

プレースホルダ以外の用途

BlurHashは主に画像読み込み中のプレースホルダとして使用されることを想定して開発されていますが、他にも用途はあり、Misskeyでは以下の用途にも活用しています。

センシティブ設定された画像のプレビュー表示
ユーザーが画像をクリックするまでのプレビューとしてBlurHashを使用することで、センシティブな画像の表示にワンクッション挟んでいます。
画像の平均色の抽出
画像全体の平均の色をBlurHashで抽出して、ユーザーアイコンの猫耳の色付けに使用したりしています。

まとめ

BlurHashを使うと、ほとんど追加のデータの必要なしに、美しいプレースホルダを表示することができます。ライブラリ自体も軽量で、いろいろな言語で利用できますので導入のハードルも低いでしょう。

Webページの読み込み中、何も表示されないのと簡易でもプレースホルダが表示されるのとでは、ユーザーに与える印象が変わります。ぜひ活用してみてください。

おすすめ記事

記事・ニュース一覧