keycap-designer 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. keycap_designer/.gitignore +1 -0
  2. keycap_designer/__init__.py +7 -0
  3. keycap_designer/__main__.py +254 -0
  4. keycap_designer/_version.py +21 -0
  5. keycap_designer/area/junana/15u-convex-front.png +0 -0
  6. keycap_designer/area/junana/15u-convex-tf.png +0 -0
  7. keycap_designer/area/junana/15u-convex-top.png +0 -0
  8. keycap_designer/area/junana/15u-front.png +0 -0
  9. keycap_designer/area/junana/15u-tf.png +0 -0
  10. keycap_designer/area/junana/15u-top.png +0 -0
  11. keycap_designer/area/junana/15u.png +0 -0
  12. keycap_designer/area/junana/1u-convex-front.png +0 -0
  13. keycap_designer/area/junana/1u-convex-tf.png +0 -0
  14. keycap_designer/area/junana/1u-convex-top.png +0 -0
  15. keycap_designer/area/junana/1u-front.png +0 -0
  16. keycap_designer/area/junana/1u-tf.png +0 -0
  17. keycap_designer/area/junana/1u-top.png +0 -0
  18. keycap_designer/area/junana/1u.png +0 -0
  19. keycap_designer/area/junana/225u-convex-front.png +0 -0
  20. keycap_designer/area/junana/225u-convex-tf.png +0 -0
  21. keycap_designer/area/junana/225u-convex-top.png +0 -0
  22. keycap_designer/area/junana/225u-front.png +0 -0
  23. keycap_designer/area/junana/225u-tf.png +0 -0
  24. keycap_designer/area/junana/225u-top.png +0 -0
  25. keycap_designer/area/junana/225u.png +0 -0
  26. keycap_designer/area/junana/note.txt +5 -0
  27. keycap_designer/area/xda/125u.png +0 -0
  28. keycap_designer/area/xda/15u.png +0 -0
  29. keycap_designer/area/xda/175u.png +0 -0
  30. keycap_designer/area/xda/1u-front.png +0 -0
  31. keycap_designer/area/xda/1u.png +0 -0
  32. keycap_designer/area/xda/225u.png +0 -0
  33. keycap_designer/area/xda/275u.png +0 -0
  34. keycap_designer/area/xda/2u.png +0 -0
  35. keycap_designer/color_management.py +176 -0
  36. keycap_designer/constants.py +75 -0
  37. keycap_designer/deform/__init__.py +0 -0
  38. keycap_designer/deform/deform_test.py +46 -0
  39. keycap_designer/deform/junana/__init__.py +0 -0
  40. keycap_designer/deform/junana/area/f15u.png +0 -0
  41. keycap_designer/deform/junana/area/f15u_convex.png +0 -0
  42. keycap_designer/deform/junana/area/f1u.png +0 -0
  43. keycap_designer/deform/junana/area/f1u_convex.png +0 -0
  44. keycap_designer/deform/junana/area/f225u.png +0 -0
  45. keycap_designer/deform/junana/area/f225u_convex-pattern.png +0 -0
  46. keycap_designer/deform/junana/area/f225u_convex.png +0 -0
  47. keycap_designer/deform/junana/f15u.py +28 -0
  48. keycap_designer/deform/junana/f15u_convex.py +28 -0
  49. keycap_designer/deform/junana/f1u.py +26 -0
  50. keycap_designer/deform/junana/f1u_convex.py +37 -0
  51. keycap_designer/deform/junana/f225u.py +28 -0
  52. keycap_designer/deform/junana/f225u_convex.py +27 -0
  53. keycap_designer/deform/junana/pattern/f1u.png +0 -0
  54. keycap_designer/deform/junana/pattern/f1u_convex.png +0 -0
  55. keycap_designer/font/NotoSansMono-VariableFont_wdth,wght.ttf +0 -0
  56. keycap_designer/font/OFL.txt +93 -0
  57. keycap_designer/icc/Linear P3D65.icc +0 -0
  58. keycap_designer/icc/sRGBz.icc +0 -0
  59. keycap_designer/icc/sublinova-epson4pigment-PBT-20231121_srgb.icc +0 -0
  60. keycap_designer/image.py +237 -0
  61. keycap_designer/manuscript.py +901 -0
  62. keycap_designer/overwrite_reportlab.py +125 -0
  63. keycap_designer/preview.py +439 -0
  64. keycap_designer/profile/__init__.py +67 -0
  65. keycap_designer/profile/junana.py +158 -0
  66. keycap_designer/profile/xda.py +59 -0
  67. keycap_designer/repo/content/all_available_junana.py +28 -0
  68. keycap_designer/repo/content/all_available_xda.py +35 -0
  69. keycap_designer/repo/content/example_ansi_104.py +133 -0
  70. keycap_designer/repo/content/starter-kit/00.png +0 -0
  71. keycap_designer/repo/content/starter-kit/01.png +0 -0
  72. keycap_designer/repo/content/starter-kit/02.png +0 -0
  73. keycap_designer/repo/content/starter-kit/03.png +0 -0
  74. keycap_designer/repo/content/starter-kit/04.png +0 -0
  75. keycap_designer/repo/content/starter-kit/05.png +0 -0
  76. keycap_designer/repo/content/starter-kit/06.png +0 -0
  77. keycap_designer/repo/content/starter-kit/07.png +0 -0
  78. keycap_designer/repo/content/starter-kit/08.png +0 -0
  79. keycap_designer/repo/content/starter-kit/09.png +0 -0
  80. keycap_designer/repo/content/starter-kit/10.png +0 -0
  81. keycap_designer/repo/content/test_image.png +0 -0
  82. keycap_designer/repo/content/test_pattern.png +0 -0
  83. keycap_designer/repo/content/tutorial_1.py +94 -0
  84. keycap_designer/repo/content/tutorial_2.py +76 -0
  85. keycap_designer/repo/content/tutorial_3.py +112 -0
  86. keycap_designer/repo/content/tutorial_junana.py +46 -0
  87. keycap_designer/repo/font/OFL.txt +93 -0
  88. keycap_designer/repo/font/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  89. keycap_designer/repo/font/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  90. keycap_designer/repo/layout/ansi-104.json +227 -0
  91. keycap_designer/repo/layout/p2ppcb-starter-kit.json +77 -0
  92. keycap_designer/repo/layout/test.json +61 -0
  93. keycap_designer/repo/vscode/launch.json +16 -0
  94. keycap_designer/repo/vscode/settings.json +10 -0
  95. keycap_designer-0.1.3.dist-info/METADATA +320 -0
  96. keycap_designer-0.1.3.dist-info/RECORD +100 -0
  97. keycap_designer-0.1.3.dist-info/WHEEL +5 -0
  98. keycap_designer-0.1.3.dist-info/entry_points.txt +2 -0
  99. keycap_designer-0.1.3.dist-info/licenses/LICENSE +21 -0
  100. keycap_designer-0.1.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ _version.py
@@ -0,0 +1,7 @@
1
+ try:
2
+ from ._version import version
3
+ __version_info__ = version
4
+ except ImportError:
5
+ __version_info__ = version = '0.0.dev1'
6
+
7
+ __version__ = '.'.join(map(str, __version_info__))
@@ -0,0 +1,254 @@
1
+ import faulthandler
2
+ faulthandler.enable()
3
+ import typing as ty
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+ import importlib
8
+ import traceback
9
+ import sys
10
+ import platform
11
+
12
+ from prompt_toolkit import PromptSession
13
+ from prompt_toolkit.completion import NestedCompleter, PathCompleter
14
+ from prompt_toolkit.history import FileHistory
15
+ from prompt_toolkit.styles import Style
16
+ from prompt_toolkit.shortcuts import yes_no_dialog
17
+
18
+ from keycap_designer.constants import CURRENT_DIR, RESOURCE_DIR
19
+ from keycap_designer.manuscript import manuscript_to_artwork
20
+ from keycap_designer.preview import print_rc_map, print_preview
21
+
22
+
23
+ CONTENT_DIR_NAME = 'content'
24
+ FONT_DIR_NAME = 'font'
25
+ LAYOUT_DIR_NAME = 'layout'
26
+ VSCODE_DIR_NAME = '.vscode'
27
+ CONTENT_DIR = CURRENT_DIR / CONTENT_DIR_NAME
28
+ OUTPUT_DIR = CURRENT_DIR / 'tmp'
29
+ REPO_DIR_NAMES = [CONTENT_DIR_NAME, FONT_DIR_NAME, LAYOUT_DIR_NAME]
30
+ REPO_DIRS = [CURRENT_DIR / d for d in REPO_DIR_NAMES]
31
+ MODULE = None
32
+ IMPORTED_MODULE_D: dict[str, ty.Any] = {}
33
+
34
+
35
+ style = Style.from_dict({
36
+ 'module_name': '#00dd00',
37
+ 'pound': 'ansicyan',
38
+ 'sheet_size': '#884444',
39
+ 'separator': '#0000aa',
40
+ 'status': '#aa0000',
41
+ })
42
+ session = PromptSession(history=FileHistory('history.txt'), style=style)
43
+
44
+
45
+ def show_pdf(pdf: Path):
46
+ s = platform.system()
47
+ if s == 'Darwin':
48
+ import shlex
49
+ subprocess.Popen([f'open {shlex.quote(str(pdf))}'], shell=True)
50
+ elif s == 'Windows':
51
+ subprocess.Popen([str(pdf)], shell=True)
52
+ else:
53
+ print(f'Platform {s} is not supported. Open {str(pdf)} by yourself.')
54
+
55
+
56
+ def show_preview():
57
+ if MODULE is None:
58
+ raise Exception('show_preview called but MODULE is None.')
59
+ if not hasattr(MODULE, 'CONTENT'):
60
+ print(f"Error: {MODULE.__name__} doesn't have CONTENT.")
61
+ return
62
+
63
+ pdf = OUTPUT_DIR / f'{MODULE.__name__}_preview.pdf'
64
+ try:
65
+ print_preview([manuscript_to_artwork(i) for i in MODULE.CONTENT], pdf)
66
+ except PermissionError:
67
+ print('Error: Close the PDF file.')
68
+ return
69
+ except FileNotFoundError as e:
70
+ print(f'File not found: {str(e)}')
71
+ return
72
+ except Exception as e:
73
+ print('Error: show_preview failed. Exception:')
74
+ traceback.print_exception(e)
75
+ return
76
+ show_pdf(pdf)
77
+
78
+
79
+ def rc_map(text: str):
80
+ layout_fn = text.split()[1]
81
+ p = Path(CURRENT_DIR / LAYOUT_DIR_NAME / layout_fn)
82
+ pdf = OUTPUT_DIR / f'{p.stem}_rc_map.pdf'
83
+ try:
84
+ print_rc_map(p, pdf)
85
+ except PermissionError:
86
+ print('Error: Close the PDF file.')
87
+ return
88
+ except Exception as e:
89
+ print('Error: rc_map failed. Exception:')
90
+ traceback.print_exception(e)
91
+ return
92
+ show_pdf(pdf)
93
+
94
+
95
+ def reload():
96
+ global MODULE
97
+ if MODULE is None:
98
+ print('Error: content not loaded yet.')
99
+ return
100
+ try:
101
+ MODULE = importlib.reload(MODULE)
102
+ IMPORTED_MODULE_D[MODULE.__name__] = MODULE
103
+ except Exception as e:
104
+ print(f'Error: {MODULE.__name__} reload failed. Exception:')
105
+ traceback.print_exception(e)
106
+ MODULE = None
107
+ return
108
+ if not hasattr(MODULE, 'CONTENT'):
109
+ print(f"Error: {MODULE.__name__} doesn't have CONTENT.")
110
+ MODULE = None
111
+ return
112
+ show_preview()
113
+
114
+
115
+ def load(text: str):
116
+ global MODULE
117
+ fn = text.split()[1]
118
+ p = Path(CONTENT_DIR / fn).relative_to(CONTENT_DIR)
119
+ module_name = str.join('.', (CONTENT_DIR_NAME, ) + p.parts[:-1] + (p.stem, ))
120
+ importlib.invalidate_caches()
121
+ try:
122
+ if module_name in IMPORTED_MODULE_D:
123
+ MODULE = importlib.reload(IMPORTED_MODULE_D[module_name])
124
+ else:
125
+ MODULE = importlib.import_module(module_name)
126
+ except Exception as e:
127
+ MODULE = None
128
+ print(f'Error: {module_name} import failed. Exception:')
129
+ traceback.print_exception(e)
130
+ return
131
+ IMPORTED_MODULE_D[module_name] = MODULE
132
+ if not hasattr(MODULE, 'CONTENT'):
133
+ print(f"Error: {module_name} doesn't have CONTENT.")
134
+ MODULE = None
135
+ return
136
+ show_preview()
137
+ return
138
+
139
+
140
+ def remove_file_if_exists(p: Path):
141
+ if not p.exists():
142
+ return True
143
+ try:
144
+ p.unlink()
145
+ except Exception:
146
+ return False
147
+ return True
148
+
149
+
150
+ def pump():
151
+ module_path_completer = PathCompleter(get_paths=lambda: [CONTENT_DIR_NAME], file_filter=lambda fn: os.path.isdir(fn) or fn.endswith('.py'))
152
+ layout_path_completer = PathCompleter(get_paths=lambda: [LAYOUT_DIR_NAME], file_filter=lambda fn: os.path.isdir(fn) or fn.endswith('.json'))
153
+ root_completer = NestedCompleter.from_nested_dict({
154
+ 'show': None,
155
+ 'reload': None,
156
+ 'load': module_path_completer,
157
+ 'rc_map': layout_path_completer,
158
+ 'help': None,
159
+ 'exit': None
160
+ })
161
+
162
+ message = []
163
+
164
+ message += [
165
+ ('class:module_name', '('),
166
+ ('class:module_name', 'None' if MODULE is None else MODULE.__name__),
167
+ ('class:module_name', ')'),
168
+ ]
169
+
170
+ message.append(('class:pound', '# '))
171
+
172
+ text: str = session.prompt(message, completer=root_completer, bottom_toolbar='To exit this app, type "exit" and hit Enter key.')
173
+
174
+ if text == 'show':
175
+ show_preview()
176
+ if text.startswith('rc_map'):
177
+ rc_map(text)
178
+ elif text == '' or text == 'reload':
179
+ reload()
180
+ elif text.startswith('load'):
181
+ load(text)
182
+ elif text.startswith('exit'):
183
+ return False
184
+ elif text.startswith('help'):
185
+ print('''Commands:
186
+ load {content script file}:
187
+ Loads the content script and shows the preview.
188
+ reload (or just hit Enter key):
189
+ Reloads current content script and shows the preview.
190
+ show:
191
+ Shows the preview of current content script without reloading.
192
+ rc_map {layout KLE JSON file}:
193
+ Shows Row/Col map of the layout KLE JSON file.
194
+ exit:
195
+ Exits this app.''')
196
+ else:
197
+ print(f'Error: {text} is invalid.')
198
+
199
+ return True
200
+
201
+
202
+ def repo_ok():
203
+ if not OUTPUT_DIR.is_dir():
204
+ if not remove_file_if_exists(OUTPUT_DIR):
205
+ print(f'Error: Cannot remove {OUTPUT_DIR} file. Please remove the file by yourself.')
206
+ sys.exit(1)
207
+ OUTPUT_DIR.mkdir()
208
+
209
+ if all(d.is_dir() for d in REPO_DIRS + [CURRENT_DIR / VSCODE_DIR_NAME]):
210
+ return True
211
+
212
+ result = yes_no_dialog(
213
+ title="keycap-designer",
214
+ text="Do you initialize current directory for keycap-designer?"
215
+ ).run()
216
+
217
+ if result:
218
+ import shutil
219
+ for n, d in zip(REPO_DIR_NAMES, REPO_DIRS):
220
+ shutil.copytree(RESOURCE_DIR / 'repo' / n, d, dirs_exist_ok=True)
221
+ shutil.copytree(RESOURCE_DIR / 'repo/vscode', CURRENT_DIR / VSCODE_DIR_NAME, dirs_exist_ok=True)
222
+
223
+ return result
224
+
225
+
226
+ def main():
227
+ from keycap_designer import version
228
+ print(f'keycap-designer {version} (C) 2023-2025 DecentKeyboards; MIT License')
229
+ if not repo_ok():
230
+ return
231
+ if len(sys.argv) > 1:
232
+ v = sys.argv[1]
233
+ try:
234
+ vp = Path(v)
235
+ except Exception:
236
+ print('Error: The arg {v} is not a valid file path.')
237
+ sys.exit(1)
238
+ try:
239
+ rp = vp.relative_to(CONTENT_DIR)
240
+ except Exception:
241
+ print('Error: The file {v} is not in ./content folder.')
242
+ sys.exit(1)
243
+ load('load ' + str(rp))
244
+
245
+ sys.path.append(os.getcwd())
246
+ print('Type "help" and hit Enter key to show the command help.')
247
+ while True:
248
+ cont = pump()
249
+ if not cont:
250
+ break
251
+
252
+
253
+ if __name__ == '__main__':
254
+ main()
@@ -0,0 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
10
+
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
14
+
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
+
20
+ __version__ = version = '0.1.3'
21
+ __version_tuple__ = version_tuple = (0, 1, 3)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ Margin line width should be 1 pt.
2
+
3
+ For top side, the inner edge of the margin line should be the surface edge of the top side. The fillets are not included.
4
+
5
+ For front side, margin line should be placed on the edge of fillet-less surface. The projection angle is 60 degree.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,176 @@
1
+ import typing as ty
2
+ from enum import Enum, auto
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from PIL import Image as PILImageModule
8
+ import cv2
9
+ import cmm
10
+ from .overwrite_reportlab import overwrite_reportlab
11
+
12
+ ICC_DIR = Path(__file__).parent / 'icc'
13
+ WORKSPACE_PROFILE_PATH = ICC_DIR / 'Linear P3D65.icc'
14
+ WS_HP = None
15
+ with open(ICC_DIR / 'sRGBz.icc', 'rb') as f:
16
+ SRGB_PROF = f.read()
17
+ overwrite_reportlab()
18
+
19
+
20
+ class RenderingIntent(Enum):
21
+ Perceptual = cmm.INTENT_PERCEPTUAL
22
+ Relative = cmm.INTENT_RELATIVE_COLORIMETRIC
23
+ Saturation = cmm.INTENT_SATURATION
24
+ Absolute = cmm.INTENT_ABSOLUTE_COLORIMETRIC
25
+
26
+
27
+ class _ColorSpaceType(Enum):
28
+ SRGB = auto()
29
+ WORKSPACE = auto()
30
+ DEVICE_RGB = auto()
31
+ DEVICE_RGB_16 = auto()
32
+
33
+
34
+ _CST_FMT = {
35
+ _ColorSpaceType.SRGB: cmm.get_transform_formatter(0, cmm.PT_RGB, 3, 1, 0, 0),
36
+ _ColorSpaceType.WORKSPACE: cmm.get_transform_formatter(0, cmm.PT_RGB, 3, 2, 0, 0),
37
+ _ColorSpaceType.DEVICE_RGB: cmm.get_transform_formatter(0, cmm.PT_RGB, 3, 1, 0, 0),
38
+ _ColorSpaceType.DEVICE_RGB_16: cmm.get_transform_formatter(0, cmm.PT_RGB, 3, 2, 0, 0)
39
+ }
40
+
41
+
42
+ with open(WORKSPACE_PROFILE_PATH, 'rb') as f:
43
+ WS_HP = cmm.open_profile_from_mem(f.read())
44
+ del f
45
+
46
+
47
+ def check_ws_img(ws_img: NDArray[np.uint16]):
48
+ if ws_img.shape[2] != 3:
49
+ raise Exception('ws_img should be BGR image')
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class _ColorConversionType:
54
+ source: _ColorSpaceType
55
+ target: _ColorSpaceType
56
+ bpc: bool
57
+ soft_proof: bool = False
58
+ soft_proof_cs: _ColorSpaceType = _ColorSpaceType.SRGB
59
+
60
+
61
+ class ColorConverter:
62
+ def __init__(self, device_rgb_profile_path: Path) -> None:
63
+ self._transform_cache: dict[tuple[_ColorConversionType, RenderingIntent], ty.Any] = {}
64
+ with open(device_rgb_profile_path, 'rb') as f:
65
+ hp = cmm.open_profile_from_mem(f.read())
66
+ self.cst_hp = {
67
+ _ColorSpaceType.SRGB: cmm.create_srgb_profile(),
68
+ _ColorSpaceType.WORKSPACE: WS_HP,
69
+ _ColorSpaceType.DEVICE_RGB: hp,
70
+ _ColorSpaceType.DEVICE_RGB_16: hp
71
+ }
72
+
73
+ def _get_transform(self, cct: _ColorConversionType, rendering_intent: RenderingIntent):
74
+ if (cct, rendering_intent) in self._transform_cache:
75
+ return self._transform_cache[cct, rendering_intent]
76
+ bpc = cmm.cmsFLAGS_BLACKPOINTCOMPENSATION if cct.bpc else 0
77
+ if cct.soft_proof:
78
+ tr = cmm.create_proofing_transform(
79
+ self.cst_hp[cct.source], _CST_FMT[cct.source],
80
+ self.cst_hp[cct.soft_proof_cs], _CST_FMT[cct.soft_proof_cs],
81
+ self.cst_hp[_ColorSpaceType.DEVICE_RGB],
82
+ rendering_intent.value, cmm.INTENT_RELATIVE_COLORIMETRIC,
83
+ bpc)
84
+ else:
85
+ tr = cmm.create_transform(
86
+ self.cst_hp[cct.source], _CST_FMT[cct.source],
87
+ self.cst_hp[cct.target], _CST_FMT[cct.target],
88
+ rendering_intent.value,
89
+ bpc)
90
+ self._transform_cache[cct, rendering_intent] = tr
91
+ return tr
92
+
93
+ def workspace_to_device_rgb(self, ws_img: NDArray[np.uint16], rendering_intent: RenderingIntent, bpc=True):
94
+ check_ws_img(ws_img)
95
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint8)
96
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.DEVICE_RGB, bpc), rendering_intent)
97
+ cmm.do_transform_16_8(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
98
+ return PILImageModule.fromarray(trg_img)
99
+
100
+ def workspace_to_device_rgb_as_cv2(self, ws_img: NDArray[np.uint16], rendering_intent: RenderingIntent, bpc=True) -> NDArray[np.uint16]:
101
+ check_ws_img(ws_img)
102
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint16)
103
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.DEVICE_RGB_16, bpc), rendering_intent)
104
+ cmm.do_transform_16_16(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
105
+ return cv2.cvtColor(trg_img, cv2.COLOR_RGB2BGR) # type: ignore
106
+
107
+ def device_rgb_as_cv2_to_workspace(self, device_rgb_img: NDArray[np.uint16]) -> NDArray[np.uint16]:
108
+ trg_img = np.zeros(device_rgb_img.shape, dtype=np.uint16)
109
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.DEVICE_RGB_16, _ColorSpaceType.WORKSPACE, False), RenderingIntent.Relative)
110
+ cmm.do_transform_16_16(tr, cv2.cvtColor(device_rgb_img, cv2.COLOR_BGR2RGB), trg_img, device_rgb_img.size // 3)
111
+ return cv2.cvtColor(trg_img, cv2.COLOR_RGB2BGR) # type: ignore
112
+
113
+ def source_to_workspace(self, pil_img: PILImageModule.Image) -> NDArray[np.uint16]:
114
+ src_fmt = cmm.get_transform_formatter(0, cmm.PT_RGB, 3, 1, 0, 0)
115
+ src_hp = None
116
+ if 'icc_profile' in pil_img.info:
117
+ profile_mem = pil_img.info['icc_profile']
118
+ src_hp = cmm.open_profile_from_mem(profile_mem)
119
+ tr = cmm.create_transform(
120
+ src_hp, src_fmt,
121
+ self.cst_hp[_ColorSpaceType.WORKSPACE], _CST_FMT[_ColorSpaceType.WORKSPACE],
122
+ cmm.INTENT_RELATIVE_COLORIMETRIC, 0)
123
+ else:
124
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.SRGB, _ColorSpaceType.WORKSPACE, False), RenderingIntent.Relative)
125
+ src_img = np.array(pil_img.convert('RGB'))
126
+ trg_img = np.zeros(src_img.shape, dtype=np.uint16)
127
+ cmm.do_transform_8_16(tr, src_img, trg_img, src_img.size // 3)
128
+ if src_hp is not None:
129
+ cmm.close_profile(src_hp)
130
+ ret = cv2.cvtColor(trg_img, cv2.COLOR_RGB2BGR)
131
+ if 'A' in pil_img.mode:
132
+ ret = cv2.cvtColor(ret, cv2.COLOR_BGR2BGRA)
133
+ ret[:, :, 3] = np.array(pil_img.getchannel('A'), np.uint16) * 257
134
+ return ret # type: ignore
135
+
136
+ def workspace_to_soft_proof(self, ws_img: NDArray[np.uint16], rendering_intent: RenderingIntent, bpc=True):
137
+ '''
138
+ The result is sRGB.
139
+ '''
140
+ check_ws_img(ws_img)
141
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint8)
142
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.DEVICE_RGB, bpc, soft_proof=True), rendering_intent)
143
+ cmm.do_transform_16_8(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
144
+ pimg = PILImageModule.fromarray(trg_img)
145
+ pimg.info['icc_profile'] = SRGB_PROF
146
+ return pimg
147
+
148
+ def workspace_to_soft_proof_as_workspace(self, ws_img: NDArray[np.uint16], rendering_intent: RenderingIntent, bpc=True) -> NDArray[np.uint16]:
149
+ check_ws_img(ws_img)
150
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint16)
151
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.DEVICE_RGB, bpc, soft_proof=True, soft_proof_cs=_ColorSpaceType.WORKSPACE), rendering_intent)
152
+ cmm.do_transform_16_16(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
153
+ return cv2.cvtColor(trg_img, cv2.COLOR_RGB2BGR) # type: ignore
154
+
155
+ def workspace_to_srgb_as_cv2(self, ws_img: NDArray[np.uint16]) -> NDArray[np.uint8]:
156
+ check_ws_img(ws_img)
157
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint8)
158
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.SRGB, False), RenderingIntent.Relative)
159
+ cmm.do_transform_16_8(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
160
+ return cv2.cvtColor(trg_img, cv2.COLOR_RGB2BGR) # type: ignore
161
+
162
+ def workspace_to_srgb(self, ws_img: NDArray[np.uint16]):
163
+ '''
164
+ For debugging.
165
+ '''
166
+ if ws_img.shape[2] == 4:
167
+ ws_img = ws_img[:, :, :3]
168
+ trg_img = np.zeros(ws_img.shape, dtype=np.uint8)
169
+ tr = self._get_transform(_ColorConversionType(_ColorSpaceType.WORKSPACE, _ColorSpaceType.SRGB, False), RenderingIntent.Relative)
170
+ cmm.do_transform_16_8(tr, cv2.cvtColor(ws_img, cv2.COLOR_BGR2RGB), trg_img, ws_img.size // 3)
171
+ pimg = PILImageModule.fromarray(trg_img)
172
+ pimg.info['icc_profile'] = SRGB_PROF
173
+ return pimg
174
+
175
+
176
+ DEFAULT_CC = ColorConverter(ICC_DIR / 'sublinova-epson4pigment-PBT-20231121_srgb.icc')
@@ -0,0 +1,75 @@
1
+ from enum import Enum, auto
2
+ from pathlib import Path
3
+ import os
4
+ import platform
5
+ from ordered_enum.ordered_enum import OrderedEnum
6
+
7
+
8
+ class Orientation(Enum):
9
+ LeftTop = auto()
10
+ Center = auto()
11
+ RightBottom = auto()
12
+
13
+
14
+ Left = Orientation.LeftTop
15
+ Center = Orientation.Center
16
+ Right = Orientation.RightBottom
17
+ Top = Left
18
+ Bottom = Right
19
+
20
+
21
+ class ImageFit(Enum):
22
+ KeepAspect = auto()
23
+ Crop = auto()
24
+ Expand = auto()
25
+ PixelWise = auto()
26
+
27
+
28
+ Aspect = ImageFit.KeepAspect
29
+ Crop = ImageFit.Crop
30
+ Expand = ImageFit.Expand
31
+ PixelWise = ImageFit.PixelWise
32
+
33
+
34
+ class ImageInterpolate(Enum):
35
+ Cubic = auto()
36
+ Nearest = auto()
37
+
38
+
39
+ Cubic = ImageInterpolate.Cubic
40
+ Nearest = ImageInterpolate.Nearest
41
+
42
+
43
+ class ImageTrim(Enum):
44
+ Inner = auto()
45
+ Outer = auto()
46
+
47
+
48
+ TrimInner = ImageTrim.Inner
49
+ TrimOuter = ImageTrim.Outer
50
+
51
+
52
+ class Side(OrderedEnum):
53
+ Top = auto()
54
+ Front = auto()
55
+ TopFront = auto()
56
+
57
+
58
+ TopSide = Side.Top
59
+ FrontSide = Side.Front
60
+
61
+
62
+ CURRENT_DIR = Path(os.getcwd())
63
+ RESOURCE_DIR = Path(os.path.dirname(__file__))
64
+ OS_FONT_DIR = {
65
+ 'Windows': Path(r'C:\Windows\Fonts'),
66
+ 'Darwin': Path('/Library/Fonts'),
67
+ 'Linux': Path('/usr/share/fonts')
68
+ }[platform.system()]
69
+ APP_FONT_DIR = CURRENT_DIR / 'font'
70
+ DESC_FONT_PATH = RESOURCE_DIR / 'font/NotoSansMono-VariableFont_wdth,wght.ttf'
71
+ DPI = 720
72
+ IPM = 25.4
73
+ DPM = DPI / IPM
74
+ MAX_RANK = 100
75
+ MAX_N_CB = 10
File without changes
@@ -0,0 +1,46 @@
1
+ from importlib import import_module
2
+ from pathlib import Path
3
+ import numpy as np
4
+ from skimage.transform import PiecewiseAffineTransform, warp
5
+ import cv2
6
+
7
+
8
+ AREA_NAME = 'f1u_convex'
9
+ DATA_DIR = Path(__file__).parent / "junana"
10
+ AREA = True
11
+
12
+ d = 'area' if AREA else 'pattern'
13
+ IMG_FILENAME = str(DATA_DIR / f"{d}/{AREA_NAME}.png")
14
+
15
+
16
+ image = cv2.imread(IMG_FILENAME, cv2.IMREAD_UNCHANGED)
17
+ if AREA:
18
+ alpha = image[:, :, 3].copy()
19
+ image[alpha == 0] = 255
20
+ image[:, :, 3] = alpha
21
+ else:
22
+ i = np.full(image.shape[:2] + (4, ), 255, np.uint8)
23
+ i[:, :, :3] = image
24
+ image = i
25
+ image[:, :, 3] = 255 - image[:, :, 3] # type: ignore
26
+ rows, cols = image.shape[:2]
27
+
28
+ landmarks = import_module(f'junana.{AREA_NAME}').TABLE
29
+ dst = landmarks[:, 0]
30
+ src = landmarks[:, 1]
31
+ if AREA:
32
+ src, dst = dst, src
33
+
34
+ out_cols, out_rows = src.max(axis=0)
35
+
36
+ tform = PiecewiseAffineTransform()
37
+ _ = tform.estimate(src, dst)
38
+
39
+ out = (warp(image, tform, output_shape=(out_rows, out_cols), cval=0.5) * 255).astype(np.uint8)
40
+
41
+ out[:, :, 3] = 255 - out[:, :, 3]
42
+
43
+ cv2.imshow('img', out)
44
+ cv2.waitKey(0)
45
+ cv2.destroyAllWindows()
46
+ cv2.imwrite('tmp/out.png', out)
File without changes