Coverage for paintmaskanimator/replacement_sets.py: 96%

41 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 04:50 +0000

1"""使用色の色置換セットの保存と読み込み(Issue #42)。 

2 

3シーン色への置き換えなど、カットをまたいで繰り返し使う色置換がある。毎回 

4登録し直さずに済むよう、「元の色 → 置き換える色」の組をファイル(``.pmarep``) 

5へ出し入れする。Qt に依存しない素の辞書として扱い、UI は 

6``main_window_used_color`` 側が受け持つ。 

7""" 

8import json 

9from pathlib import Path 

10 

11#: ファイルの拡張子とダイアログのフィルター。 

12SUFFIX = ".pmarep" 

13FILE_FILTER = "Paint Mask Replacements (*.pmarep)" 

14FORMAT_VERSION = 1 

15 

16 

17def _hex(rgb): 

18 red, green, blue = (max(0, min(255, int(value))) for value in rgb[:3]) 

19 return f"#{red:02x}{green:02x}{blue:02x}" 

20 

21 

22def _rgb(value): 

23 text = str(value).strip().lstrip("#") 

24 if len(text) != 6: 

25 raise ValueError(value) 

26 return tuple(int(text[index:index + 2], 16) for index in (0, 2, 4)) 

27 

28 

29def to_data(mapping): 

30 """``{元の RGB: 置換後の RGB}`` を、書き出せる素のデータにする。""" 

31 return { 

32 "format": "paintmaskanimator.replacements", 

33 "version": FORMAT_VERSION, 

34 "replacements": [ 

35 {"from": _hex(source), "to": _hex(destination)} 

36 for source, destination in mapping.items() 

37 ], 

38 } 

39 

40 

41def from_data(data): 

42 """読み込んだデータを ``{元の RGB: 置換後の RGB}`` にする。 

43 

44 読めない組は飛ばす。手で書き換えたファイルや、将来の項目が増えた 

45 ファイルでも、分かるところだけ使えるようにするため。 

46 """ 

47 if not isinstance(data, dict): 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true

48 raise ValueError("replacement set must be an object") 

49 entries = data.get("replacements") 

50 if not isinstance(entries, list): 

51 raise ValueError("replacement set has no replacements") 

52 mapping = {} 

53 for entry in entries: 

54 if not isinstance(entry, dict): 

55 continue 

56 try: 

57 source = _rgb(entry["from"]) 

58 destination = _rgb(entry["to"]) 

59 except (KeyError, TypeError, ValueError): 

60 continue 

61 if source != destination: 

62 mapping[source] = destination 

63 return mapping 

64 

65 

66def save(path, mapping): 

67 path = Path(path) 

68 if path.suffix.lower() != SUFFIX: 

69 path = path.with_suffix(SUFFIX) 

70 path.write_text( 

71 json.dumps(to_data(mapping), ensure_ascii=False, indent=2), 

72 encoding="utf-8", 

73 ) 

74 return path 

75 

76 

77def load(path): 

78 return from_data(json.loads(Path(path).read_text(encoding="utf-8")))