🔥

映像をAA風にリアルタイムで描画するのを、作った

に公開

経緯

作りたかったから

フォルダ構造

.
├── index.html
├── index.js
└── video.mp4

コード

index.html

<!DOCTYPE html>
<html lang="js">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<video id="video" controls src="video.mp4" style="position: absolute; top: 0; left: 0; width: 10%; aspect-ratio: 16/9;"></video>
<canvas id="previewCanvas" style="width: 100%; aspect-ratio: 16/9;"></canvas>
<script src="index.js"></script>
</body>
</html>

index.js

function $(id) {return document.getElementById(id)}
const resolve = 10;

let frameCanvas, preCanvas;
let frameCtx, preCtx;
let width, height;
let offscreen, offCtx;
let charWidth, charHeight;

let lastFrameTime = 0;

// const chars = '■WMBDOQGpqbdwmZXYUJCLftjri|}{][)(><+~-_:;,^`\'. ';
const chars = '■■■■@@@@@%WMBDOQGpqbdwmZXYUJCLftjri#####|}{][)(*+=-:. ';
// const chars = '■'
const FPS = 24;
const frameDuration = 1000/FPS;

function handleVideoFrame(now) {
    const timeLastFrame = now - lastFrameTime;

    if(timeLastFrame >= frameDuration) {
        lastFrameTime = now;

        preCtx.clearRect(0, 0, preCanvas.width, preCanvas.height);
        offCtx.clearRect(0, 0, width, height);
        offCtx.drawImage(this.video, 0, 0, width, height);

        let imageData = offCtx.getImageData(0, 0, width, height);
        let pixels = imageData.data;

        for(let y = 0; y < height; y++) {
            for(let x = 0; x < width; x++) {
                const base = (y * width + x) * 4;
                const [r, g, b] = [pixels[base], pixels[base+1], pixels[base+2]];
                const brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255;
                const charIndex = Math.floor(brightness * (chars.length - 1));
                const char = chars[charIndex];
                preCtx.fillStyle = `rgb(${r}, ${g}, ${b})`;
                preCtx.fillText(char, x * charWidth, y * charHeight);
            }
        }
    }

    this.video.requestVideoFrameCallback(handleVideoFrame);
}

function init() {
    const video = $("video");
    preCanvas = $("previewCanvas");
    const rect = preCanvas.getBoundingClientRect();
    width = Math.floor(rect.width / resolve);
    height = Math.floor(rect.height / resolve);

    preCanvas.width = rect.width;
    preCanvas.height = rect.height;

  // 1文字あたりのサイズを計算
    charWidth = rect.width / width;
    charHeight = rect.height / height;
    
    // コンテキスト設定
    preCtx = preCanvas.getContext("2d");
    
    // フォントサイズを文字サイズに合わせて設定
    const fontSize = Math.min(charWidth, charHeight) * 1;
    preCtx.font = `bold ${Math.floor(fontSize)}px monospace`;
    preCtx.textBaseline = 'top';
    preCtx.textAlign = 'left';
    preCtx.imageSmoothingEnabled = false;

    offscreen = new OffscreenCanvas(width, height);
    offCtx = offscreen.getContext("2d");
    offCtx.imageSmoothingEnabled = false;

    video.requestVideoFrameCallback(handleVideoFrame.bind( {video} ));
}

document.addEventListener('DOMContentLoaded', () => {
    $("video").addEventListener('loadedmetadata', init);
});

使い方

VSCodeとかのLive Serverで、index.htmlを立ち上げて動画を再生開始するだけ
お粗末なコードなので、たまにコンテンツが読み込まれなくて、再生しても描画されないことがあります

Discussion