2
4

バイキュービック補間で、画像をアップサンプリングするゲーム。

Last updated at Posted at 2024-10-03

image.png

スペースキーを押すと、現在のマウス位置を中心に画像をアップサンプリングします。

コードをメモ帳などのテキストエディタに貼り付け、ファイルを「index.html」などの拡張子が.htmlのファイルとして保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。

バイキュービック補間(Bicubic Interpolation)は、画像処理で用いられる技術で、画像の拡大や縮小時にピクセル値を滑らかに補完します。バイキュービック補間は、周囲の16ピクセル(4x4のピクセルブロック)を使用して新しいピクセルの値を計算します。

バイキュービック補間は、距離に応じて各点に重みを付け、その重み付きの平均値を計算する手法です。そして、この重みの変化は、三次多項式によって滑らかに変化するように設計されています。

具体的には、補間する対象点に対して、最も近い4つのデータポイント(1次元の場合)や16個のデータポイント(2次元の場合)を選び、それぞれの点の距離に基づいた重みを三次多項式で計算します。これにより、隣接するデータ点が補間結果にどれだけ影響を与えるかが決まります。

この三次多項式の特徴は、滑らかに値を補完することで、近傍のデータ点に依存しつつも、全体の変化が急激にならないようにします。

まとめると:

重みは対象点と隣接点の距離に応じて計算され、
三次多項式を用いることで、補間が滑らかかつ自然な形で行われる
ということになります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>バイキュービック補間によるアップサンプリング</title>
    <style>
        #canvas {
            border: 1px solid black; /* キャンバスの枠線 */
            cursor: crosshair; /* マウスカーソルを十字に設定 */
        }
    </style>
</head>
<body>
    <h1>画像アップサンプリングデモ</h1>
    <input type="file" id="upload" accept="image/*" /> <!-- 画像ファイルを選択するための入力 -->
    <canvas id="canvas"></canvas> <!-- 画像を表示するためのキャンバス -->
    <script>
        const upload = document.getElementById('upload'); // アップロード用のファイル入力
        const canvas = document.getElementById('canvas'); // キャンバス要素
        const ctx = canvas.getContext('2d'); // 2Dコンテキストを取得
        let originalImage = null; // アップロードされたオリジナル画像
        let mouseX = 0; // マウスのX座標
        let mouseY = 0; // マウスのY座標

        // 画像をアップロードするイベントリスナー
        upload.addEventListener('change', (event) => {
            const file = event.target.files[0]; // 選択されたファイルを取得
            if (file) {
                const reader = new FileReader(); // FileReaderオブジェクトを作成
                reader.onload = (e) => {
                    const img = new Image(); // 画像オブジェクトを作成
                    img.onload = () => {
                        originalImage = img; // 画像が読み込まれたらオリジナル画像に設定
                        canvas.width = img.width; // キャンバスの幅を画像の幅に設定
                        canvas.height = img.height; // キャンバスの高さを画像の高さに設定
                        ctx.drawImage(img, 0, 0); // オリジナル画像をキャンバスに描画
                    };
                    img.src = e.target.result; // 読み込んだデータを画像ソースに設定
                };
                reader.readAsDataURL(file); // ファイルをデータURLとして読み込む
            }
        });

        // マウスの動きに応じて座標を更新するイベントリスナー
        canvas.addEventListener('mousemove', (event) => {
            const rect = canvas.getBoundingClientRect(); // キャンバスの位置とサイズを取得
            mouseX = event.clientX - rect.left; // マウスのX座標をキャンバスの左端からの距離に変換
            mouseY = event.clientY - rect.top; // マウスのY座標をキャンバスの上端からの距離に変換

            // マウスカーソルの座標を表示
            ctx.clearRect(0, 0, canvas.width, canvas.height); // キャンバスをクリア
            if (originalImage) {
                ctx.drawImage(originalImage, 0, 0); // オリジナル画像を再描画
            }
            ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // 赤い色を半透明で設定
            ctx.fillRect(mouseX - 5, mouseY - 5, 10, 10); // マウスカーソル位置を示す四角を描画
        });

        // スペースキーを押したときにアップサンプリングを実行
        document.addEventListener('keydown', (event) => {
            if (event.code === 'Space' && originalImage) { // スペースキーが押された場合
                upsample(mouseX, mouseY); // 現在のマウス位置を中心にアップサンプリングを実行
            }
        });

        // アップサンプリング処理を行う関数
        function upsample(centerX, centerY) {
            const scale = 2; // アップサンプリングのスケール
            const newWidth = originalImage.width * scale; // 新しい幅(元の画像の幅 × スケール)
            const newHeight = originalImage.height * scale; // 新しい高さ(元の画像の高さ × スケール)

            // 一時的なキャンバスを作成
            const tempCanvas = document.createElement('canvas'); // 一時キャンバスを作成
            const tempCtx = tempCanvas.getContext('2d'); // 一時キャンバスの2Dコンテキストを取得
            tempCanvas.width = newWidth; // 一時キャンバスの幅を設定
            tempCanvas.height = newHeight; // 一時キャンバスの高さを設定

            // バイキュービック補間で拡大
            tempCtx.imageSmoothingEnabled = true; // 画像スムージングを有効化
            tempCtx.imageSmoothingQuality = 'high'; // 高品質スムージングを設定
            tempCtx.drawImage(originalImage, 0, 0, originalImage.width, originalImage.height, 
                               0, 0, newWidth, newHeight); // オリジナル画像を拡大して一時キャンバスに描画

            // 中心座標に合わせてアップサンプリングされた部分を描画
            const displaySize = 400; // 表示する領域のサイズ
            const startX = Math.max(0, (centerX * scale) - displaySize / 2); // 描画開始X座標
            const startY = Math.max(0, (centerY * scale) - displaySize / 2); // 描画開始Y座標

            // 拡大された画像をメインキャンバスに描画
            ctx.clearRect(0, 0, canvas.width, canvas.height); // メインキャンバスをクリア
            ctx.drawImage(originalImage, 0, 0); // 元の画像を再描画
            ctx.drawImage(tempCanvas, startX, startY, displaySize, displaySize, 
                          centerX - displaySize / 2, centerY - displaySize / 2, 
                          displaySize, displaySize); // アップサンプリングされた部分を描画
        }
    </script>
</body>
</html>

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4