タイトル: 「拡散する未来」
東京の午後、浅川翔太は薄暗い自室のモニターをじっと見つめていた。彼はプログラマーとして数々の案件をこなしてきたが、最近手掛けているプロジェクトは少し変わっていた。新たなアルゴリズムを使い、画像を拡散させ、元の状態に復元するというものだ。彼はこのアルゴリズムに自分でも驚くほどの興味を抱いていた。拡散と逆拡散――それはただの数学的な処理ではなく、翔太にとっては何か人生そのものを象徴しているように感じられた。
翔太が作ったプログラムは、ユーザーが提供した画像に「拡散係数」をかけ、徐々にその輝度を失わせ、最終的には消失させる。そして、その勾配情報を基に逆にたどって、画像を元に戻すというものだ。理論はシンプルだが、実際にその過程をアニメーションで見たとき、まるで画像が生と死を繰り返しているような感覚を翔太は覚えた。
その日は長時間のコーディングに疲れ、少し休憩することにした。翔太はコーヒーを淹れて、窓から外を見た。東京の空は灰色の雲に覆われ、ビル群が霞むように立ち並んでいた。彼はふと、最近の自分の生活について考えた。日々の仕事、終わりの見えない締め切り、いつしか感情も淡く拡散していくように、かつての情熱も薄れている気がしていた。
「このプログラム、まるで俺みたいだな」
彼は自嘲するように笑った。自分自身も、仕事に追われる中でどんどん拡散しているように感じていた。だが、プログラムが示す通り、一度拡散しても、勾配をたどれば元に戻ることができる。そう思った瞬間、翔太は何かを掴んだ気がした。
彼は席に戻り、コーディングを再開した。コードを走らせると、モニターには512x512ピクセルの画像が表示された。最初は鮮明だった画像が、少しずつ暗く、そして曖昧になっていく。その過程は50ステップにわたり、徐々にその形が失われていった。ついには、画面上に残るのはほとんど何もない状態になった。
「消えたか…」
翔太は呟いた。しかし、プログラムはまだ終わっていない。逆拡散が始まる。ピクセルが少しずつ、元の位置に戻り、失われた形が復元されていく。その光景を見つめながら、翔太は心の中で小さな希望の灯がともったのを感じた。彼自身も、拡散しきった後にこそ、逆にたどる力があるのかもしれない。
そして、画像が完全に復元された時、翔太は静かにキーボードを打ち始めた。新しいプロジェクトのためのコードを書き始める。拡散の過程は終わったが、次の段階が彼を待っているのだ。
人生もまた、消えてしまいそうな時こそ、その勾配をたどって、もう一度輝くことができる――翔太はそう信じることにした。
翔太のプログラムは静かにモニターの中で動き続けていた。それは、拡散と逆拡散のサイクルを繰り返しながら、彼自身の未来の姿を映し出しているかのようだった。
コードをメモ帳などのテキストエディタに貼り付け、ファイル名を「index.html」として保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
<!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;
}
#upload {
margin-bottom: 10px;
}
#status {
font-size: 24px;
font-weight: bold;
position: absolute;
top: 10px;
left: 10px;
color: white;
background-color: rgba(0, 0, 0, 0.7);
padding: 5px;
border-radius: 5px;
}
</style>
</head>
<body>
<h2>画像拡散と逆拡散アニメーション</h2>
<input type="file" id="upload" accept="image/*">
<canvas id="canvas" width="512" height="512"></canvas>
<div id="status">状態: なし</div> <!-- 状態表示用のdiv -->
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const statusDiv = document.getElementById('status'); // 状態表示用divの取得
let originalImageData = null;
let diffusionImageData = null;
let gradientData = null; // 勾配データを保存
document.getElementById('upload').addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, 512, 512);
originalImageData = ctx.getImageData(0, 0, 512, 512);
diffusionImageData = ctx.createImageData(512, 512);
gradientData = new Float32Array(originalImageData.data.length); // 勾配データの初期化
step = 0; // ステップをリセット
statusDiv.textContent = "状態: 拡散中"; // 拡散中の状態を表示
animateDiffusion();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
});
let step = 0;
const maxSteps = 50; // ステップ数を50に設定
const diffusionFactor = 0.95; // 拡散係数
const reverseFactor = 0.15; // 逆拡散のステップごとの係数
// 画像を拡散するアニメーション
function animateDiffusion() {
if (step < maxSteps) {
applyDiffusion(diffusionFactor);
step++;
setTimeout(animateDiffusion, 100); // 100ミリ秒ごとに次のステップを呼び出し
} else {
step = 0;
statusDiv.textContent = "状態: 逆拡散中"; // 逆拡散中の状態を表示
setTimeout(animateReverseDiffusion, 100);
}
}
// 拡散処理: 各ピクセルのRGB値を減らしていく
function applyDiffusion(factor) {
for (let i = 0; i < originalImageData.data.length; i += 4) {
for (let j = 0; j < 3; j++) { // RGBの3チャンネル
const originalValue = originalImageData.data[i + j];
gradientData[i + j] = originalValue * (1 - factor); // 勾配情報を保存
diffusionImageData.data[i + j] = originalValue * (1 - (step / maxSteps)); // ステップに基づいて色を薄くする
}
diffusionImageData.data[i + 3] = originalImageData.data[i + 3]; // アルファチャンネルはそのまま
}
ctx.putImageData(diffusionImageData, 0, 0);
}
// 逆拡散アニメーション: 徐々に元に戻す
function animateReverseDiffusion() {
if (step < maxSteps) {
applyReverseDiffusion();
step++;
setTimeout(animateReverseDiffusion, 100); // 100ミリ秒ごとに次のステップを呼び出し
} else {
step = 0;
statusDiv.textContent = "状態: 拡散中"; // 拡散中の状態を表示し、再度拡散アニメーションを開始
setTimeout(animateDiffusion, 100);
}
}
// 逆拡散処理: 勾配データを用いて元の画像に戻す
function applyReverseDiffusion() {
for (let i = 0; i < diffusionImageData.data.length; i += 4) {
for (let j = 0; j < 3; j++) { // RGBの3チャンネル
const restoredValue = diffusionImageData.data[i + j] + gradientData[i + j]; // 勾配に基づいてRGB値を復元
diffusionImageData.data[i + j] = Math.min(255, Math.max(0, restoredValue)); // 値を0から255の範囲にクリップ
}
diffusionImageData.data[i + 3] = originalImageData.data[i + 3]; // アルファチャンネルはそのまま
}
ctx.putImageData(diffusionImageData, 0, 0); // キャンバスに描画
}
</script>
</body>
</html>