Coverage for graphica/core/cvd_simulation.py: 100.0%

16 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""色覚のシミュレーション。GUI には依存しない。 

2 

3sRGB に直接掛ける近似の行列(Brettel / Viénot を簡略化したもの)で、プレビュー用。各行の和が 1 なので白・黒・灰は変わらない。 

4""" 

5import numpy as np 

6 

7# 1型(赤)・2型(緑)・3型(青) 

8CVD_MATRICES = { 

9 'protanopia': np.array([ 

10 [0.567, 0.433, 0.000], 

11 [0.558, 0.442, 0.000], 

12 [0.000, 0.242, 0.758], 

13 ]), 

14 'deuteranopia': np.array([ 

15 [0.625, 0.375, 0.000], 

16 [0.700, 0.300, 0.000], 

17 [0.000, 0.300, 0.700], 

18 ]), 

19 'tritanopia': np.array([ 

20 [0.950, 0.050, 0.000], 

21 [0.000, 0.433, 0.567], 

22 [0.000, 0.475, 0.525], 

23 ]), 

24} 

25 

26CVD_TYPE_LABELS = { 

27 'protanopia': '1型(P型、赤色覚異常)', 

28 'deuteranopia': '2型(D型、緑色覚異常)', 

29 'tritanopia': '3型(T型、青色覚異常)', 

30} 

31 

32 

33def simulate_rgb_array(rgb_array: np.ndarray, cvd_type: str) -> np.ndarray: 

34 """形が (..., 3) の RGB に行列を掛ける。0〜255 のままでよい(スケールは保たれる)。クリップと型の変換は呼び出し側。""" 

35 if cvd_type not in CVD_MATRICES: 

36 raise ValueError(f"未知の色覚タイプです: {cvd_type}") 

37 matrix = CVD_MATRICES[cvd_type] 

38 original_shape = rgb_array.shape 

39 flat = np.asarray(rgb_array, dtype=np.float64).reshape(-1, 3) 

40 simulated = flat @ matrix.T 

41 return simulated.reshape(original_shape) 

42 

43 

44def simulate_hex_color(hex_color: str, cvd_type: str) -> str: 

45 """#RRGGBB を1つ変換する(画像を介さない単色用)。""" 

46 hex_color = hex_color.lstrip('#') 

47 rgb = np.array([int(hex_color[i:i + 2], 16) for i in (0, 2, 4)], dtype=np.float64) 

48 simulated = np.clip(simulate_rgb_array(rgb, cvd_type), 0, 255).astype(np.uint8) 

49 return '#' + ''.join(f'{v:02x}' for v in simulated)