Oh MyUtils

色盲模拟器 - 色觉无障碍测试 在线

模拟红色盲、绿色盲和蓝色盲患者看到的颜色。检查WCAG对比度比率,确保无障碍网页设计。

常见问题

什么是色盲模拟器?

色盲模拟器是一种在线工具,显示色觉缺陷(CVD)患者看到的颜色和图像效果。它应用经过科学验证的算法来转换颜色,帮助设计师和开发人员了解大约8%的男性和0.5%的女性色觉异常者如何感知他们的视觉内容。

如何使用这个工具?

此工具有两种模式。颜色模式:使用HEX值、RGB值或颜色选择器输入前景色和背景色,工具会立即显示这些颜色在所有8种色觉缺陷类型下的效果以及WCAG对比度。图像模式:上传图像(拖放或文件浏览),选择CVD类型,即可看到模拟版本与原始图像的并排对比。您还可以下载模拟图像用于文档或演示。

我的数据安全吗?会发送到服务器吗?

您的数据100%安全,永远不会离开您的浏览器。所有颜色计算、WCAG对比度检查和图像处理完全在客户端使用JavaScript和Canvas API执行。没有任何颜色或图像会传输到任何服务器,因此可以安全地测试专有设计、机密品牌资产和敏感的视觉内容。

8种色觉缺陷类型是什么?

8种类型分别是:红色盲(无红色锥体,红色显得暗淡)、绿色盲(无绿色锥体,最常见的二色视觉)、蓝色盲(无蓝色锥体,蓝黄混淆)、红色弱(红色敏感度降低)、绿色弱(绿色敏感度降低,影响约5%男性的最常见CVD)、蓝色弱(蓝色敏感度降低,非常罕见)、全色盲(完全色盲,只能看到灰度)和部分色盲(部分色觉缺失,色彩感知严重降低)。

WCAG对比度检查器如何与色盲模拟配合工作?

工具计算前景色和背景色之间的标准WCAG 2.1对比度,然后在应用每种CVD模拟后重新计算对比度。这可以发现颜色组合在正常视力下通过WCAG标准但对色觉缺陷者来说不合格的情况——这是实现真正无障碍设计的关键洞察。

这个工具使用什么模拟算法?

此工具使用经过同行评审、科学验证的算法:二色视觉类型(红色盲、绿色盲、蓝色盲)使用Brettel, Vienot & Mollon(1997)算法,异常三色视觉类型(红色弱、绿色弱、蓝色弱)使用Machado, Oliveira & Fernandes(2009)算法。全色盲使用ITU-R BT.709亮度加权。为确保精度,所有转换都在线性RGB色彩空间中执行。

我可以用这个工具测试图像吗?

可以。图像选项卡允许您上传最大4096x4096像素的图像(JPG、PNG、WebP、GIF)。工具使用Canvas API应用像素级CVD模拟,并排显示原始图像和模拟图像。您可以下载模拟图像用于无障碍审计或文档编制。

-anopia(色盲)和-anomaly(色弱)类型有什么区别?

后缀"-anopia(色盲)"表示某种锥体类型的完全缺失(二色视觉),而"-anomaly(色弱)"表示敏感度降低(异常三色视觉)。例如,Protanopia(红色盲)意味着完全没有红色锥体,而Protanomaly(红色弱)意味着红色锥体存在但敏感度发生了偏移。色弱类型通常比色盲类型程度更轻且更常见。

代码示例

// 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"})`);
}

相关工具