Algorithm & UI/UX
Canvas 2Dで実現する「レンズ内の拡大」とレスポンシブ座標変換の舞台裏
単なる円形のトリミングではなく、望遠鏡特有の「覗き込んでいる感覚」を演出するにはどうすればよいか。クリッピング領域の制御と、ピクセル解像度の動的マッピングについて解説します。
このツールの入力から出力まで
現行コードの確認日:
- 入力
- 写真を選び、レンズのサイズ・暗さ・歪みを調整します。
- 処理
- 固定中心の円で写真をクリップし、円内を一様に拡大し、放射状グラデーションで縁を暗くします。
- 出力
- 入力の長辺を 2000px 以下に縮小し、1000×1000px の JPEG(品質指定 0.85)を保存します。
まず確認する点:歪みは場所ごとの倍率を変える魚眼ではありません。写真を動かす操作とレンズを大きくする操作を分けて試します。
仕上がりの例
写真を丸く切り抜き、望遠鏡の視界風にした例です。
掲載画像はサイトにある既存の公開作例です。保存済みの資料では運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。
保存済み検証記録の条件を見る
資料の種類は「ツールの出力画像を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。
- 記録の入力画像
- 800×751px (JPEG)
- 記録の出力画像
- 1000×1000px (JPEG)
| 画面の項目 | 記録の値 | 現行コードの初期値・範囲 |
|---|---|---|
| レンズの大きさ | 60 | 初期値 60 / 範囲 20〜100 |
| レンズ内の拡大 | 40 | 初期値 40 / 範囲 0〜100 |
| フチの暗さ(周辺減光) | 70 | 初期値 70 / 範囲 0〜100 |
| 写真のズーム | 100 | 初期値 100 / 範囲 50〜300 |
記録では「おもしろ画像を生成!」から完成表示へ進んでいます。初期値と記録値を比べてから、効果を1項目ずつ変えると作用を確認しやすくなります。
資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。
実装の確認箇所:src/pages/image/telescope-view/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。
仕組みを読み進めるときは、Canvas APIと画像処理(画像寸法、drawImage、画素と透明度の扱い)、File APIとObject URL(選んだ画像の読み込みとBlob URLの解放)が参考になります。
1. レスポンシブなドラッグ操作を支える「正規化座標マッピング」
このツールでは、Canvasの描画サイズを固定(例:1000x1000px)しつつ、画面上ではCSSによって伸縮させています。このとき、ユーザーがスマホでタップした座標(CSSピクセル)を、Canvas内部の座標系に正確に変換する必要があります。
getBoundingClientRect() から得られる実際の表示幅と、Canvasの論理的な解像度の比率を用いることで、表示上の移動量を処理画像の座標へ換算します。
レンズ設定を描画値に変換する例です。distortion は画面上の「歪み」の設定ですが、計算は一様な拡大です。
function lensSettings(size, distortion, darkness) {
const radius = 450 * size / 100;
return { radius, magnification: 1 + distortion / 100 * 0.5,
gradientStart: radius * (1 - darkness / 150),
edgeAlpha: darkness / 100 };
} 2. クリッピング領域内での「レンズ内の拡大」シミュレーション
本ツールの核心となるアルゴリズムは、ctx.clip() による円形マスクと、マスク内での動的な拡大率制御の組み合わせです。
ただ円形に切り取るだけでは平面的な印象になりますが、本実装ではレンズ越しの拡大感を表現するため、クリッピング領域内でのみ画像をさらに拡大(Magnification)させています。スライダーの値に基づいて画像の中心点をオフセットさせつつスケールをかけることで、レンズ越しに覗いているような奥行きのある視覚効果を生み出しています。
3. Radial Gradientによる周辺減光(ビネット)の演出
望遠鏡のリアリティを決定づけるのが、円のフチに向かって暗くなっていく「周辺減光」です。これを実現するために、円形クリッピングの境界に合わせて createRadialGradient を生成しています。
グラデーションの開始地点をレンズ半径の内側に設定し、境界線(エッジ)に向かって不透明度を高めることで、視線を中央へ誘導するシネマティックな質感を加えています。
出力の仕様と確認する点
入力の長辺を 2000px 以下に縮小し、1000×1000px の JPEG(品質指定 0.85)を保存します。円の外側は黒で塗り、透明な円形画像を出力する設定はありません。
レンズサイズ 20〜100 で半径 90〜450px、歪み 0〜100 で拡大率 1〜1.5 倍です。円内の画像全体を均等に拡大するため、魚眼のように場所ごとに倍率が変わる歪曲収差とは異なります。
円の中心は固定で、写真をドラッグして見える範囲を変えます。暗さは縁の不透明度と減光開始位置を同時に変えます。暗さ 0 でも外側の黒い領域と縁取りは残ります。
