Oh MyUtils

色覚シミュレーター - 色覚異常アクセシビリティテスト オンライン

1型色覚、2型色覚、3型色覚の方に色がどう見えるかシミュレート。WCAGコントラスト比でアクセシブルなWebデザインを確認。

よくある質問

色覚シミュレーターとは何ですか?

色覚シミュレーターは、色覚異常(CVD)を持つ人々に色や画像がどのように見えるかを表示するオンラインツールです。科学的に検証されたアルゴリズムを適用して色を変換し、デザイナーや開発者が、何らかの色覚異常を持つ男性の約8%、女性の約0.5%に視覚コンテンツがどのように認識されるかを理解するのに役立ちます。

このツールはどのように使いますか?

このツールには2つのモードがあります。カラーモード:HEX値、RGB値、またはカラーピッカーを使用して前景色と背景色を入力すると、WCAGコントラスト比とともに8種類すべての色覚異常でこれらの色がどのように見えるかを即座に表示します。画像モード:画像をアップロード(ドラッグ&ドロップまたはファイル参照)し、CVDタイプを選択すると、オリジナルとシミュレーション版を並べて表示します。ドキュメントやプレゼンテーション用にシミュレーション画像をダウンロードすることもできます。

データは安全ですか?サーバーに送信されますか?

データは100%安全で、ブラウザの外に出ることはありません。すべての色計算、WCAGコントラストチェック、画像処理は、JavaScriptとCanvas APIを使用して完全にクライアントサイドで実行されます。色や画像がサーバーに送信されることはないため、独自のデザイン、機密のブランドアセット、センシティブな視覚コンテンツのテストに安全に使用できます。

8種類の色覚異常とは何ですか?

8種類は以下の通りです:1型色覚異常(赤錐体なし、赤が暗く見える)、2型色覚異常(緑錐体なし、最も一般的な二色型)、3型色覚異常(青錐体なし、青黄の混同)、1型色弱(赤感度低下)、2型色弱(緑感度低下、男性の約5%に影響する最も一般的なCVD)、3型色弱(青感度低下、非常にまれ)、全色盲(完全な色覚異常、グレースケールのみ)、部分色盲(部分的な色覚異常、著しく低下した色知覚)。

WCAGコントラストチェッカーは色覚シミュレーションとどのように連動しますか?

ツールは前景色と背景色の間の標準WCAG 2.1コントラスト比を計算し、各CVDシミュレーションを適用した後にコントラストを再計算します。これにより、通常の視力ではWCAG基準を通過するが、色覚異常者には不合格となるケースを発見できます。これは真にアクセシブルなデザインのための重要な洞察です。

このツールはどのシミュレーションアルゴリズムを使用していますか?

このツールは査読済みの科学的に検証されたアルゴリズムを使用しています:二色型(1型色覚異常、2型色覚異常、3型色覚異常)にはBrettel, Vienot & Mollon(1997)、異常三色型(1型色弱、2型色弱、3型色弱)にはMachado, Oliveira & Fernandes(2009)を使用します。全色盲にはITU-R BT.709輝度加重を使用します。精度のため、すべての変換はリニアRGB色空間で実行されます。

このツールで画像をテストできますか?

はい。画像タブでは、最大4096x4096ピクセルの画像(JPG、PNG、WebP、GIF)をアップロードできます。ツールはCanvas APIを使用してピクセルレベルのCVDシミュレーションを適用し、オリジナルとシミュレーション画像を並べて表示します。アクセシビリティ監査やドキュメント作成用にシミュレーション画像をダウンロードできます。

-anopia(色覚異常)と-anomaly(色弱)の違いは何ですか?

接尾辞「-anopia(色覚異常)」は錐体タイプの完全な欠如(二色型)を意味し、「-anomaly(色弱)」は感度の低下(異常三色型)を意味します。例えば、Protanopia(1型色覚異常)は赤錐体が全くないことを意味し、Protanomaly(1型色弱)は赤錐体は存在するが感度が変化していることを意味します。色弱タイプは一般的に色覚異常タイプより軽度で、より一般的です。

コード例

// Color Blindness Simulator - JavaScript Implementation

/**
 * sRGB to Linear RGB conversion
 */
function srgbToLinear(c) {
  const v = c / 255;
  return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}

function linearToSrgb(c) {
  const v = Math.max(0, Math.min(1, c));
  return Math.round(
    (v <= 0.0031308 ? 12.92 * v : 1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255
  );
}

/**
 * CVD simulation matrices (Brettel 1997 / Machado 2009)
 * Each is a 3x3 matrix stored as flat array [row-major]
 */
const CVD_MATRICES = {
  protanopia: [
    0.152286, 1.052583, -0.204868,
    0.114503, 0.786281, 0.099216,
    -0.003882, -0.048116, 1.051998,
  ],
  deuteranopia: [
    0.367322, 0.860646, -0.227968,
    0.280085, 0.672501, 0.047413,
    -0.011820, 0.042940, 0.968881,
  ],
  tritanopia: [
    1.255528, -0.076749, -0.178779,
    -0.078411, 0.930809, 0.147602,
    0.004733, 0.691367, 0.303900,
  ],
  achromatopsia: [
    0.2126, 0.7152, 0.0722,
    0.2126, 0.7152, 0.0722,
    0.2126, 0.7152, 0.0722,
  ],
};

/**
 * Simulate color vision deficiency for a single RGB color
 * @param {number} r - Red (0-255)
 * @param {number} g - Green (0-255)
 * @param {number} b - Blue (0-255)
 * @param {string} cvdType - CVD type key
 * @param {number} severity - 0.0 to 1.0
 * @returns {number[]} Simulated [r, g, b]
 */
function simulateColor(r, g, b, cvdType, severity = 1.0) {
  const matrix = CVD_MATRICES[cvdType];
  if (!matrix) throw new Error(`Unknown CVD type: ${cvdType}`);

  // Convert to linear
  const lr = srgbToLinear(r);
  const lg = srgbToLinear(g);
  const lb = srgbToLinear(b);

  // Apply matrix
  const sr = matrix[0] * lr + matrix[1] * lg + matrix[2] * lb;
  const sg = matrix[3] * lr + matrix[4] * lg + matrix[5] * lb;
  const sb = matrix[6] * lr + matrix[7] * lg + matrix[8] * lb;

  // Lerp with original for severity
  const fr = lr * (1 - severity) + sr * severity;
  const fg = lg * (1 - severity) + sg * severity;
  const fb = lb * (1 - severity) + sb * severity;

  return [linearToSrgb(fr), linearToSrgb(fg), linearToSrgb(fb)];
}

/**
 * Calculate WCAG 2.1 contrast ratio
 */
function getContrastRatio(fg, bg) {
  const lum = (r, g, b) =>
    0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);

  const l1 = lum(...fg);
  const l2 = lum(...bg);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

/**
 * Check WCAG compliance
 */
function checkWCAG(ratio) {
  return {
    aa: { normalText: ratio >= 4.5, largeText: ratio >= 3 },
    aaa: { normalText: ratio >= 7, largeText: ratio >= 4.5 },
  };
}

// Usage
const fg = [255, 102, 0]; // Orange
const bg = [255, 255, 255]; // White

console.log("Original contrast:", getContrastRatio(fg, bg).toFixed(2) + ":1");

const cvdTypes = ["protanopia", "deuteranopia", "tritanopia", "achromatopsia"];
for (const type of cvdTypes) {
  const simFg = simulateColor(...fg, type);
  const simBg = simulateColor(...bg, type);
  const ratio = getContrastRatio(simFg, simBg);
  const wcag = checkWCAG(ratio);
  console.log(`${type}: ${ratio.toFixed(2)}:1 (AA: ${wcag.aa.normalText ? "PASS" : "FAIL"})`);
}

関連ツール