Algorithm & UI/UX
Canvasで実現する「鏡の反射」と回転対応のヒット判定
本記事では、画像を反転させて重ね合わせることで「鏡らしさ」を演出する反射ロジックと、自由な角度に配置されたオブジェクトを正確に操作するための座標変換アルゴリズムについて深掘りします。
1. 反転描画による疑似反射アルゴリズム
「鏡越し自撮り」の最大の特徴は、スマホの画面内に「撮影者側の風景」が写り込んでいる点です。これを再現するために、単に画像を重ねるのではなく、**元画像を左右反転(水平フリップ)させた上で、スマホの画面領域だけにクリッピングして描画**する手法を採っています。
実装のメリット
- リアリティの向上: 鏡は像を反転させる性質があるため、反転描画を行うことで脳が「鏡である」と直感的に認識します。
- 軽量な合成処理: 複雑なレイトレーシングやシェーダーを使わず、Canvas標準の
scale(-1, 1)とclip()だけで完結しているため、モバイルブラウザでも非常に高速に動作します。
// スマホ画面の反射を描画するコアロジック(簡略化)
function drawReflection(ctx, image, phoneState) {
ctx.save();
// スマホの画面領域のみを切り抜き
ctx.beginPath();
ctx.roundRect(/* 画面の座標とサイズ */, // 独自の角丸しきい値);
ctx.clip();
// 映り込みの「透け感」を設定
ctx.globalAlpha = phoneState.opacity / 100;
// 左右反転の座標変換
ctx.scale(-1, 1);
// 反転した状態で元画像を描画(座標系は独自のチューニングにより算出)
ctx.drawImage(image, -canvasWidth, 0, canvasWidth, canvasHeight);
ctx.restore();
} 2. 回転行列を利用した逆座標変換ヒット判定
ユーザーがスマホを自由な角度に回転(-45°〜45°)できるUIにおいて、ドラッグ操作を成立させるには「クリックした地点が、回転したスマホの範囲内か」を判定する必要があります。ここでは、判定処理を単純化するために、**マウス座標をスマホの回転角度に合わせて逆回転させる**ことで、軸に平行な長方形としての判定を可能にしています。
実装のメリット
- 計算コストの低減: 複雑な多角形の当たり判定(交差判定)を行う代わりに、一次方程式の範囲チェックだけで済むため、毎フレームの判定が極めて軽量です。
- 直感的な操作感: 回転していても境界線ギリギリまで正確に反応するため、ユーザーにストレスを与えません。
// 回転した矩形に対するヒット判定
function isHit(pointX, pointY, rect) {
const rad = -rect.angle * Math.PI / 180; // 逆回転の角度
// 中心点からの相対座標を算出
const dx = pointX - rect.x;
const dy = pointY - rect.y;
// 回転行列による逆座標変換
const rotatedX = dx * Math.cos(rad) - dy * Math.sin(rad);
const rotatedY = dx * Math.sin(rad) + dy * Math.cos(rad);
// 回転を無効化した状態での範囲チェック
return (
rotatedX >= -rect.width / 2 &&
rotatedX <= rect.width / 2 &&
rotatedY >= -rect.height / 2 &&
rotatedY <= rect.height / 2
);
} Developer's Note
このツールの開発において最もこだわったのは、「エモさ」の再現と「プライバシー」の両立です。ミラーセルフィー特有の、あの「なんとなくおしゃれに見える」感覚を数値化するのは難しかったのですが、最終的にはスマホのベゼルの質感やレンズの光沢、そしてわずかなブラー(ぼかし)を Canvas の filter プロパティで加えることで解決しました。
また、画像加工は非常に重い処理になりがちですが、ユーザーの貴重な思い出(写真)をサーバーに送ることなく、すべてブラウザ内のメモリで完結させるアーキテクチャを採用しています。これにより、セキュリティ的な安心感を提供しつつ、通信待ちのないサクサクとした操作感を実現できました。