Algorithm & UI/UX
黄金角サンプリングによる高品質ブラーと二段階描画の最適化
本ツールでは、タイムラインで際立つ3:4比率の画像を生成する際、単なる単色の余白ではなく、被写体を活かした「背景ぼかし」を実現しています。ブラウザ負荷を抑えつつ、商業デザインのような滑らかなボケ感を作るためのコア・ロジックを紐解きます。
1. 黄金角スパイラルを利用した高密度サンプリング・ブラー
Canvasの filter = 'blur()' は手軽ですが、ブラウザの実装や解像度によってパフォーマンスが著しく低下したり、フチが白く抜けるなどの課題があります。本ツールでは、低解像度のテンポラリCanvasを作成し、それを「黄金角(約137.5度)」に基づいて螺旋状にオフセットさせながら多重描画する独自アルゴリズムを採用しています。
この手法のメリットは、単純な円状サンプリングよりも少ない試行回数(反復)で視覚的な隙間を埋めることができ、モバイル端末でも非常に滑らかなグラデーションを得られる点にあります。
// 黄金角スパイラルを用いた多重描画によるブラー処理
function applyHighDensityBlur(ctx, sourceCanvas, width, height) {
const iterations = // 独自のサンプリング回数;
const GOLDEN_ANGLE = // 黄金角に基づく定数;
ctx.save();
ctx.globalAlpha = // 透過度の動的計算;
for (let i = 0; i < iterations; i++) {
// 螺旋状に配置するための計算
const t = i / iterations;
const angle = i * GOLDEN_ANGLE;
const radius = Math.sqrt(t) * // ぼかし強度定数;
const offX = Math.cos(angle) * radius;
const offY = Math.sin(angle) * radius;
// 微小なサイズ補正を加えながら描画し、フチの白抜けを防止
ctx.drawImage(
sourceCanvas,
offX - (width * // 補正係数),
offY - (height * // 補正係数),
width * // 拡大比率,
height * // 拡大比率
);
}
ctx.restore();
}
2. プレビューと本番を分ける「二段階パイプライン」
高解像度(1200x1600px以上)の画像をリアルタイムでぼかし計算すると、ユーザーがオプションを変更するたびにUIがフリーズしてしまいます。これを防ぐため、内部では「表示用の軽量Canvas(Preview)」と「保存用の高精細Canvas(Final)」の二段階で処理を行っています。
ユーザーが操作している間は、オリジナルの50%以下の解像度で計算を行い、保存ボタンが押された瞬間にのみバックグラウンドでフルサイズレンダリングを実行することで、サクサクとした操作感と高品質な出力を両立しています。
// レンダリング・パイプラインの切り分けロジック
function renderThumbnail(isPreview) {
const targetCanvas = isPreview ? previewCanvas : fullCanvas;
// プレビュー時は描画スケールを落として計算負荷を軽減
const scale = isPreview ? // プレビュー用縮小率 : 1.0;
// キャンバスサイズの決定
targetCanvas.width = // 基本幅 * scale;
targetCanvas.height = // 基本高さ * scale;
// ここに描画メインロジック(ブラー・シャドウ・画像合成)が入ります
// ...
}
Developer's Note
X(旧Twitter)の仕様変更に合わせて「3:4」という縦長比率が重要になりましたが、ただ画像を配置するだけでは面白くありません。ツールとしての「手触りの良さ」を追求するため、特に背景ぼかしの「重さ」との戦いに時間を割きました。
今回の実装では、黄金角サンプリングを導入することで、標準の blur フィルタよりもエッジが綺麗で、かつ調整が効くボケ感を作ることができました。また、iPhoneでよくあるHEIC形式の自動変換もライブラリの動的ロードによって実装し、初期読み込み速度を犠牲にせずに使い勝手を向上させています。