画像の色覚シミュレーション
JavaScript
色覚シミュレーションツール
基準となる色を選択してください:
通常の色
プロタノピア
デューテラノピア
トリタノピア
画像の色覚シミュレーション
画像を読み込む:
シミュレーション結果のサムネイル
オリジナル
プロタノピア
デューテラノピア
トリタノピア
拡大表示する画像を選択:
拡大表示
プログラム
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>色覚シミュレーションツール</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 2rem;
}
/* 色ブロック用スタイル */
.color-display {
width: 100px;
height: 100px;
border: 1px solid #ccc;
text-align: center;
line-height: 100px;
font-weight: bold;
}
.section {
margin-bottom: 2rem;
}
/* 色選択部分の横並び */
.color-container {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.color-item {
text-align: center;
}
/* サムネイル用キャンバスの横並び */
.canvas-container {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.canvas-item {
text-align: center;
}
/* サムネイルとして表示するキャンバスは横幅を固定し高さは自動でアスペクト比維持 */
.thumbnail-canvas {
width: 150px;
height: auto;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>色覚シミュレーションツール</h1>
<!-- カラーピッカーによる色シミュレーション -->
<div class="section">
<p>基準となる色を選択してください:</p>
<input type="color" id="colorPicker" value="#ff0000">
</div>
<!-- 色シミュレーション結果(色ブロック) -->
<div class="section color-container">
<div class="color-item">
<p>通常の色</p>
<div id="normalColor" class="color-display"></div>
</div>
<div class="color-item">
<p>プロタノピア</p>
<div id="protanopiaColor" class="color-display"></div>
</div>
<div class="color-item">
<p>デューテラノピア</p>
<div id="deuteranopiaColor" class="color-display"></div>
</div>
<div class="color-item">
<p>トリタノピア</p>
<div id="tritanopiaColor" class="color-display"></div>
</div>
</div>
<!-- 画像のシミュレーション -->
<div class="section">
<h2>画像の色覚シミュレーション</h2>
<p>画像を読み込む:</p>
<input type="file" id="imageInput" accept="image/*">
<!-- サムネイル表示:4つのキャンバスを縮小して横並びに -->
<p>シミュレーション結果のサムネイル</p>
<div class="canvas-container">
<div class="canvas-item">
<p>オリジナル</p>
<canvas id="originalCanvas" class="thumbnail-canvas"></canvas>
</div>
<div class="canvas-item">
<p>プロタノピア</p>
<canvas id="protoCanvas" class="thumbnail-canvas"></canvas>
</div>
<div class="canvas-item">
<p>デューテラノピア</p>
<canvas id="deuteranopiaCanvas" class="thumbnail-canvas"></canvas>
</div>
<div class="canvas-item">
<p>トリタノピア</p>
<canvas id="tritanopiaCanvas" class="thumbnail-canvas"></canvas>
</div>
</div>
<!-- ラジオボタンで拡大表示する画像を選択 -->
<div>
<p>拡大表示する画像を選択:</p>
<label><input type="radio" name="simType" value="original" checked> オリジナル</label>
<label><input type="radio" name="simType" value="protanopia"> プロタノピア</label>
<label><input type="radio" name="simType" value="deuteranopia"> デューテラノピア</label>
<label><input type="radio" name="simType" value="tritanopia"> トリタノピア</label>
</div>
<!-- 拡大表示用キャンバス -->
<div>
<p>拡大表示</p>
<canvas id="displayCanvas" style="border:1px solid #000;"></canvas>
</div>
</div>
<script>
// --- 基本のカラー変換処理 ---
function hexToRgb(hex) {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(h => h + h).join('');
}
const intVal = parseInt(hex, 16);
return { r: (intVal >> 16) & 255, g: (intVal >> 8) & 255, b: intVal & 255 };
}
function rgbToHex(r, g, b) {
const toHex = x => {
const hex = Math.round(x).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return '#' + toHex(r) + toHex(g) + toHex(b);
}
// シミュレーション関数(簡易版)
function simulateProtanopia(r, g, b) {
const newR = 0.567 * r + 0.433 * g;
const newG = 0.558 * r + 0.442 * g;
const newB = 0.242 * g + 0.758 * b;
return { r: newR, g: newG, b: newB };
}
function simulateDeuteranopia(r, g, b) {
const newR = 0.625 * r + 0.375 * g;
const newG = 0.70 * r + 0.30 * g;
const newB = 0.30 * g + 0.70 * b;
return { r: newR, g: newG, b: newB };
}
function simulateTritanopia(r, g, b) {
const newR = 0.95 * r + 0.05 * g;
const newG = 0.433 * g + 0.567 * b;
const newB = 0.475 * g + 0.525 * b;
return { r: newR, g: newG, b: newB };
}
// --- カラーピッカー更新 ---
function updateColors() {
const colorPicker = document.getElementById('colorPicker');
const hexColor = colorPicker.value;
const rgb = hexToRgb(hexColor);
// 通常の色
const normalDiv = document.getElementById('normalColor');
normalDiv.style.backgroundColor = hexColor;
normalDiv.textContent = hexColor.toUpperCase();
// プロタノピア
const proto = simulateProtanopia(rgb.r, rgb.g, rgb.b);
const protoHex = rgbToHex(proto.r, proto.g, proto.b);
const protoDiv = document.getElementById('protanopiaColor');
protoDiv.style.backgroundColor = protoHex;
protoDiv.textContent = protoHex.toUpperCase();
// デューテラノピア
const deuto = simulateDeuteranopia(rgb.r, rgb.g, rgb.b);
const deutoHex = rgbToHex(deuto.r, deuto.g, deuto.b);
const deuteranopiaDiv = document.getElementById('deuteranopiaColor');
deuteranopiaDiv.style.backgroundColor = deutoHex;
deuteranopiaDiv.textContent = deutoHex.toUpperCase();
// トリタノピア
const trito = simulateTritanopia(rgb.r, rgb.g, rgb.b);
const tritoHex = rgbToHex(trito.r, trito.g, trito.b);
const tritanopiaDiv = document.getElementById('tritanopiaColor');
tritanopiaDiv.style.backgroundColor = tritoHex;
tritanopiaDiv.textContent = tritoHex.toUpperCase();
}
document.getElementById('colorPicker').addEventListener('input', updateColors);
updateColors();
// --- 画像処理 ---
// 各画素ごとにシミュレーションを適用
function applySimulationToImageData(imageData, simulationFn) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
const converted = simulationFn(r, g, b);
data[i] = Math.min(255, Math.max(0, converted.r));
data[i + 1] = Math.min(255, Math.max(0, converted.g));
data[i + 2] = Math.min(255, Math.max(0, converted.b));
}
return imageData;
}
document.getElementById('imageInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
const width = img.width, height = img.height;
// サムネイル用キャンバスの内部解像度は画像サイズとする
const canvases = [
document.getElementById('originalCanvas'),
document.getElementById('protoCanvas'),
document.getElementById('deuteranopiaCanvas'),
document.getElementById('tritanopiaCanvas')
];
canvases.forEach(canvas => {
canvas.width = width;
canvas.height = height;
});
// オリジナル画像を描画
const originalCtx = document.getElementById('originalCanvas').getContext('2d');
originalCtx.drawImage(img, 0, 0, width, height);
const originalData = originalCtx.getImageData(0, 0, width, height);
// プロタノピアの描画
const protoCtx = document.getElementById('protoCanvas').getContext('2d');
let protoImageData = new ImageData(new Uint8ClampedArray(originalData.data), width, height);
protoImageData = applySimulationToImageData(protoImageData, simulateProtanopia);
protoCtx.putImageData(protoImageData, 0, 0);
// デューテラノピアの描画
const deuteranopiaCtx = document.getElementById('deuteranopiaCanvas').getContext('2d');
let deuteranopiaImageData = new ImageData(new Uint8ClampedArray(originalData.data), width, height);
deuteranopiaImageData = applySimulationToImageData(deuteranopiaImageData, simulateDeuteranopia);
deuteranopiaCtx.putImageData(deuteranopiaImageData, 0, 0);
// トリタノピアの描画
const tritanopiaCtx = document.getElementById('tritanopiaCanvas').getContext('2d');
let tritanopiaImageData = new ImageData(new Uint8ClampedArray(originalData.data), width, height);
tritanopiaImageData = applySimulationToImageData(tritanopiaImageData, simulateTritanopia);
tritanopiaCtx.putImageData(tritanopiaImageData, 0, 0);
// サムネイル描画後、ラジオボタンに応じた拡大表示を更新
updateDisplay();
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
// --- 拡大表示 ---
function updateDisplay() {
const selectedValue = document.querySelector('input[name="simType"]:checked').value;
let sourceCanvas;
if (selectedValue === "original") {
sourceCanvas = document.getElementById('originalCanvas');
} else if (selectedValue === "protanopia") {
sourceCanvas = document.getElementById('protoCanvas');
} else if (selectedValue === "deuteranopia") {
sourceCanvas = document.getElementById('deuteranopiaCanvas');
} else if (selectedValue === "tritanopia") {
sourceCanvas = document.getElementById('tritanopiaCanvas');
}
if (!sourceCanvas) return;
const displayCanvas = document.getElementById('displayCanvas');
const dCtx = displayCanvas.getContext('2d');
// 拡大倍率(例として2倍)
const scaleFactor = 2;
displayCanvas.width = sourceCanvas.width * scaleFactor;
displayCanvas.height = sourceCanvas.height * scaleFactor;
dCtx.clearRect(0, 0, displayCanvas.width, displayCanvas.height);
dCtx.drawImage(sourceCanvas, 0, 0, displayCanvas.width, displayCanvas.height);
}
document.querySelectorAll('input[name="simType"]').forEach(radio => {
radio.addEventListener('change', updateDisplay);
});
</script>
</body>
</html>
ディスカッション
コメント一覧
まだ、コメントがありません