Algorithm & UI/UX
CanvasによるRPGステータス画面の動的生成技術
ブラウザ上での画像編集を滑らかにするため、本ツールではCanvas 2D APIを駆使した独自のレンダリング・アルゴリズムを採用しています。サーバーを一切介さないプライバシー重視の設計と、自由度の高い操作性を両立した実装の工夫を解説します。
1. ポートレートの動的座標計算とクリッピング
ユーザーがアップロードした画像を「特定の枠(ポートレート枠)」の中に収めつつ、ドラッグやズームで自由に調整できる機能は、単純なCSSでは実現が難しい部分です。Canvasの clip() メソッドと、独自の座標変換ロジックを組み合わせることで、直感的な操作感を実現しています。
なぜこの実装なのか?
画像の絶対座標ではなく、枠の中心を基準とした相対的な変位を計算することで、ズーム時でも「見ている場所」がズレにくい安定した挙動を提供しています。また、描画の度にコンテキストの状態を保存・復元(save/restore)することで、他のUI要素(ステータスバーなど)にクリッピングの影響を与えない設計にしています。
// ポートレート描画のコア・ロジック(コンセプト)
function drawPortrait(ctx, img, config) {
ctx.save();
ctx.beginPath();
// 描画範囲を特定の矩形に制限
ctx.rect(PORTRAIT_X, PORTRAIT_Y, PORTRAIT_W, PORTRAIT_H);
ctx.clip();
// スライダー値とドラッグ座標に基づいた座標計算
const zoomLevel = config.baseScale * (config.userZoom / 100);
// 常に画像が枠内での相対位置を維持するための計算
// ここに座標変換のアルゴリズムが入ります
const drawX = /* 独自のオフセット計算 */;
const drawY = /* 独自のオフセット計算 */;
ctx.drawImage(img, drawX, drawY, img.width * zoomLevel, img.height * zoomLevel);
ctx.restore();
} 2. メモリ消費を抑える「読み込み時リサイズ」ハック
近年のスマートフォンの写真は非常に高解像度であり、そのままCanvasで扱うとメモリ不足によるクラッシュや、レンダリングの遅延を招きます。本ツールでは、画像をメモリ上に読み込んだ瞬間に、内部処理に最適な解像度へ自動リサイズする前処理レイヤーを設けています。
なぜこの実装なのか?
最終的な出力品質を保ちつつ、編集中のフレームレート(FPS)を維持するためです。一度適切なサイズにダウンサンプリングされた ImageBitmap や Canvas オブジェクトを使い回すことで、スクロールやドラッグ操作が非常に軽量になります。
// 画像の事前最適化処理
async function optimizeImage(img) {
const THRESHOLD = // 独自のチューニング値;
if (img.width > THRESHOLD || img.height > THRESHOLD) {
const ratio = // アスペクト比を維持した計算処理;
const offscreen = document.createElement('canvas');
offscreen.width = img.width * ratio;
offscreen.height = img.height * ratio;
const ctx = offscreen.getContext('2d');
ctx.drawImage(img, 0, 0, offscreen.width, offscreen.height);
// 縮小した画像をソースとして再定義
return await loadImageFromDataURL(offscreen.toDataURL('image/jpeg', 0.9));
}
return img;
} 3. レイヤード・レンダリングによる即時プレビュー
名前や職業、各種パラメータが変更されるたびに、Canvas全体を再描画する「リアクティブな描画システム」を構築しています。フォントのレンダリングにおいて、視認性を高めるためのシャドウ処理や、ステータスバーの動的な長さ計算を一括で処理しています。
なぜこの実装なのか?
ゲームのような「リアルタイム感」を出すためには、入力値の変化を即座にCanvasへ反映させる必要があります。HTML要素を重ねるのではなく、すべてを一枚のCanvasに描くことで、最終的な「画像保存」時の整合性を完璧に保つことができます。
// ステータスバーの描画ロジック
function drawStatBar(ctx, x, y, value, maxValue, color) {
const BAR_WIDTH = // 独自のレイアウト値;
// 背景(溝)の描画
ctx.fillStyle = '#333';
ctx.fillRect(x, y, BAR_WIDTH, // 独自の高さ);
// 数値に基づいた割合の計算
const percent = Math.min(1, value / maxValue);
// バー本体の描画
ctx.fillStyle = color;
// ここにイージングや比率計算に基づく描画処理が入ります
ctx.fillRect(x, y, BAR_WIDTH * percent, // 独自の高さ);
} Developer's Note
RPGのステータス画面という「ワクワク感」を、いかにWebツールとしてストレスなく提供できるかにこだわりました。特にスマホユーザーが多いため、HEIC形式の変換処理(heic2any)を動的にインポートして読み込み速度を落とさないようにしたり、iPhoneの仕様を考慮した長押し保存のガイダンスを用意したりと、技術的な解決策をUI/UXに落とし込んでいます。画像処理という重くなりがちなタスクを、完全にクライアントサイドで完結させることで、ユーザーに「安全で速い」体験を届けることができました。